Sustainable Software: How to design software that is sustainable
Personally, I often saw when visiting several companies that many developers were strongly dependent on ‘Spring’, which was always unfortunate.
Recently, I put together some materials for developers’ education, and I’ll post some of the contents here.
I would appreciate it if you could see this article as one of the ways even if it is obviously not the ideal solution.
Many companies go through countless iterations of new generation system development.
And in certain companies, this cycle moves quite quickly. It shows patterns like; new generation system(1year+@) -> failure after system opening(2month) -> stabilization(5month) -> new generation system(1year+@).
I believe it results in a lot of waste — lost money, maybe even wasted lives.
I am very interested in developing sustainable software. As a result, I continue to put a lot of work and training into expanding software.
The three main areas for discussing sustainable software are as follows.
I’ll start by discussing the purpose of the business logic I seek and how to codify it.
Second, the management and development of the layers of software will be covered.
Finally, I will discuss the many perspectives on modularization and how to expand a module.
Business logic is the first topic.
Alternative business logic has been bandied around a lot, but what does it really mean?
Before we talk about the business logic.
Let’s take a look at the structure and code of software that we have previously seen or can view, using Example-Software.
A Spring-based API server has been set up in Example-Software.
Controller, Service, and Repository make form the code’s structure.
The request seems to be received by the Controller, handled by the Service, and then brought via the Repository.
So where in this framework would the business logic be written?
By tradition or inertia, business logic is written at the ‘Service’.
What would the code look like if we have this structure?
Although I’m not quite sure what it works, I believe you can see some code that looks like this. Let’s take a look at it one by one.
The orange portion is being injected from multiple Repositories.
The red portion has logic for a particular function.
Several functionalities used in red are included in the pink portion.
What then does the business logic of this service mean?
To learn more, let’s take a look at the code in the red part.
Let’s quickly review the code. A function called businessPay is sent a request object and a user ID as factors.
Below that, the user and store are received and compared to ensure accuracy.
The logic then seems to execute separate logic depending on storeItem.
type after retrieving the storeItem and comparing it to the request.
The point is then retrieved and subtracted using user.id.
There is also a portion that produces a new point if the searched point doesn’t exist.
For the last, the payment has been established and saved, the function ends.
What business logic underlies this function, then?
Compared to business logic, it seems more like intricate and detail implementation logic.
It seems that this Service does an excessive amount of work given that it performs tasks such directly requesting and validating data, processing payments, and adding and subtracting points.
Does it look possible to describe the business logic based on this code to a new employee such that he can comprehend it and find it easy to learn the job?
I don’t perfectly satisfy with this.
Given that it was developed as an example, the basis I presented may be weak.
However, if codes are written in this manner, the more intricate business logic we encounter in the real world would be far more complex.
What business logic do I want to discuss and explore next, and how do I go about developing it?
I will now talk about the business logic that I think as we look at the code together.
The meaning of the business logic of sustainable software.
The functionality of this code is same as the an Example-Software version.
Although I am also unsure of the specific implementation rationale for this code, I believe I can infer some of its general flow.
Let’s quickly take a look at it line by line.
First, the function argument has a component that has changed. Instead of receiving and utilizing the request, there is a part that receives it in a form with a clearer intent (targetStore, usePoint).
First. it takes user
Second, it takes store. :: At this point, a StoreGrade object is generated and supplied as the user.type. (It is likely presumed that it is the store’s authorization data that the user type may access.)
Third, usePoint is used based on the user’s points.
For the last, add payment details as the user and store.
So far was the processing flow with guessing work. Of course, each class seems to require to validate the specific implementation logic.
The fact that it is possible to deduce this service’s function flow is the positive part though. Additionally, the work of service itself has significantly decreased.
It has no logic and does not directly make payments, produce points, or query and validates data.
Its function is to communicate, according to business flow, the collaboration classes to each role.
Additionally, It also shows that each collaborator class is explicitly responsible for doing one thing.
The proper business logic for sustainable software, in my view, may be summed up in the following one line.
Even if you are unaware of the specific implementation logic, the business flow need to be clear.
It would be nice to be at a level where, in addition to new developers, the person in charge of business or sales could see the code and say, “Oh~ this is basically how it flows.” while doing so.
Let’s move on to the next topic.
The software layer is a crucial component of sustainable software.
I believe that if we design suitable layers for each project or product as a team or component, and if we have control and authority over the code as we grow or contract as we develop, we can produce value quicker, more, and longer.
When I don’t have anything unique, I often designate the layer above as the default layer.
Let’s take a look step-by-step.
The Presentation Layer is a very externally dependent and change-sensitive area.
This layer also includes the request and response classes as well as the code in charge of managing the external area.
The business layer is where business logic is projected.
Naturally, you construct layers higher up if the code keeps expanding and the business logic becomes too much or has to be consolidated.
The Implement Layer contains classes with thorough implementation logic, as a tool to accomplish the business logic shown in the preceding example, Because it contains the most classes and is in charge of implementation logic, this core layer is highly reusable.
Data Access Layer is layer that offers tools for intricate implementation logic to access numerous resources.
Its feature separates technological dependencies and gives implementation logic a clear interface. (They are often given as distinct modules to optimize this.)
Layers don’t just end with composition, layers also need to be managed with the proper constraints.
Let’s explore the four rules I often use to control the basic layer.
When referring to layers, one must always do it in the forward manner, moving down from the top.
It is a straightforward, fundamental, yet crucial rule.
A layer’s direction of reference cannot flow backward.
In terms of coding, it is uncommon for a UserService to have a UserController.
The rule states that the UserReader must not be aware of the UserService if there is a UserService in the business layer and a class named UserReader in the implementation layer.
Layer references must not skip lower layers.
Numerous Repositories in the Service are also known by Example-Software code, which we previously examined as an example.
As a result, the business layer ends up having an excessive amount of knowledge about the specific implementation technology and logic.
This form becomes a stumbling block at some point when trying to maintain and expand the software for a long time.
In order to avoid issues like these, the implementation layer uses the data access layer to solve detailed implementation logic, and the business layer uses the implementation layer to solve business logic.
To avoid layer contamination, it is a rule to keep the implementation technology or logic a hidden from the area in charge of the business.
The same layer must not refer to each other.
(The exception being that implementation layers may refer to one another.)
This rule enables the development of more comprehensive, collaborative tool classes while increasing the reusability of implement layer classes.
Additionally, it features a rule to encourage the reuse of well-made implementations and to avoid the tainting of business logic.
So far were the main guidelines for sustainable software that I normally include in my layers.
My own main rule is to promote open standards while preserving the originality and independence of thought of internal developers.
My workplace establishes a standard layer, and it is advised that any team members who make modifications or additions to the standard document and manage them in README.md.
Through this, I have seen again how easily new developers can fit in and blend in, and as a result, I am putting together more thorough discussions based on this.
Let’s now talk about the Module that are directly connected to the software layer.
In order to create sustainable software, proper modularity is crucial.
Furthermore, it makes advantage of the separation between modules to carefully manage and take control of the program.
Let’s explore modules while examining the program used as Example-Software.
Example-Software begins the service by first developing simple software. It seems to have set up WEB and JPA dependencies in the API Server module.
But as the number of client and company’s requirements increase, so does the software.
That’s how the program that Example-Software developed will appear.
The API Server module grew in size as a result of the addition of many dependencies and an increasing number of needs. (I hope the module isn’t contaminated controlled by external dependencies.)
There are likely to be many different instances of contamination, such as the widespread usage of classes from external dependencies in business or complex implementation logic, or the direct use of enum classes.
It was necessary in this case to setup and create a Batch application.
In such case, in what form would Example-Software expand?
Example-Software seems to have gone with the simplest option. It seems that he used the dependencies exactly as they were and just once configured the Batch module.
His fundamental purpose might be seen as having been accomplished since he reacted to the urgent requirements and corporate development.
But as time went on, a circumstance emerged where the approach needed to be altered in order to perform better.
For example, he might have to remove JPA and replace it with something more potent.
However, it won’t be simple to get rid of JPA if Example-Software’s API module is already strongly connected with it.
Obviously, if you work slowly, it is conceivable, but along the process, business logic or implementation logic may be impacted.
It is the case of having too many dependencies ensnares a module. But if you leave it alone, it will only grow until you can no longer afford it and may force you to create a new generation system.
Literally, you are in a circumstance where you are dependent on something.
Although I used JPA as an example, this problem could include any dependency.
Then, I will explain about how I configure modules for sustainable software.
The method I use to setup the module is as follows.
The first step is to create fundamental dependencies like ‘cloud-config’ and ‘logging’ as modules and set them to depend on each other for the first module composition.
The basic set up is completed, then now have the development requirements.
The first requirement is the National Tax Service API integration. Let’s take a look at how the module scales to meet the requirements.
A module named ‘NTS-API Client’ was set up and the external communication component was created as a separate module in order to interface with the National Tax Service.
For the next, the requirement to save National Tax Service API results data emerged. Let’s take a look how it expand.
Using a different DB module, accessing the database is also setup.
The first requirement has been completed. However, the Payments API module’s size is unchanged.
Let’s now check the dependencies and module composition.
To begin with, non-executable modules are gathered in the modules folder for simple example viewing.
(In reality, I arrange modules into groups that are more driven by semantics.)
According to the dependency setting, the ‘Payments API module’ is unconcerned with the implementation’s selection of JPA or another tool.
Even for the ‘National Tax Service API’, there is no implementation in the Payments API module.
(There is no reference to dependencies like ‘Apache Http Component’.)
The Payments API module does not have an implementation, but the configuration used through the ‘client-nts-api’ module is shown.
Actually, it makes no interest to the Payments API module whether you communicate with external APIs using ‘HttpClient’ or ‘Feign’.
As a consequence, while examining a module’s dependency structure, the ideas it uses — rather than the technology it employs — are the main emphasis.
Now, I also have requirement that I have to add batch, in this composition
What will its form be?
Batch module is also organized into modules.
Additionally, Cloud Config and Logging must be enabled by default in order for the Payments Batch module to function.
Generally, we can see the reuse of existing modules without the duplication of extra dependencies.
For the next requirement, the Payments Batch module must use the ‘National Tax Service SFTP’
The ‘NTS-SFTP Client module’ is in charge of the specific logic or implementation technology for exchanging data with both the National Tax Service SFTP, whereas the Payments Batch module focuses on business logic.
Thus, the Payments Batch module is unaware of the precise technology used to transmit and receive SFTP data and you don’t have to worry about it.
Sending and receiving SFTP data are handled by the ‘NTS-SFTP Client’.
To learn more about this structure, let’s look at the module diagram and dependencies.
The ‘NTS-SFTP Client module’ has been added, and the Payments Batch module is configured to depend on it.
Again, looking at the dependency configuration, I can’t tell what implementation the client-nts-sftp module is.
Here’s something to look into for a moment, how do you organize dependencies between modules so you don’t know the details of implementation and technology?
Let’s look at the difference between ‘api’ and ‘implementation’ among Gradle’s dependency configuration keywords based on the current situation.
The ‘Payments Batch’ module is depending on the ‘NTS-SFTP Client module’.
And ‘NTS-SFTP Client’ module is depending on ‘Spring Integration SFTP’.
They are depending through ‘implementation keyword’.
To explain the ‘implementation keyword’ in light of the present situation, the ‘Batch module’ is unable to make use of related classes since it is unaware that the ‘SFTP Client module’ relies on ‘Spring Integration SFTP’ as an implementation.
This implies that only the ‘NTS-SFTP Client module’ may be used to utilize SFTP, and the ‘Payments Batch module’ is unable to access the detailed implementation technology.
How does the ‘api keyword’ operate then?
The Batch module may make reference to ‘Spring Integration SFTP’ classes and sub-dependencies if the ‘NTS-SFTP Client module’ is set up to depend on ‘Spring Integration SFTP’ as an API.
All reliance on the implementation technology will be transmitted to the Batch module, and the dependency reference scope is broadened.
After propagation of dependencies, developers may unintentionally contaminate the parent module.
In other words, the ‘api keyword’ makes it highly likely for the layer to get contaminated in an instant and for numerous errors to take place.
Therefore, in my case, I am expanding the module in a manner that uses the ‘implementation keyword’ as much as possible.
By adjusting to the requirements, this compostion was made.
It is impossible to describe every detail as it is based on an example, but the composition differs significantly from Example-Software’s API module.
Let’s now talk about the issue with the technological modification that occurred in Example-Software’s API module earlier.
In order to improve performance, a situation has developed where the database access method has to be changed. What will happen this time?
Let’s first try to study the module structure in more detail.
Fortunately,the ‘DB module’ was depending on JPA as the implementation keyword.
Which means, The JPA dependencies did not extend to the ‘Payments API module’.
The Payments API module was initially created using a structure that is unaware of whether it utilizes JPA.
Through this, we were able to establish that the Payments API and JPA modules are separate, and we discovered that the DB module is the only place where JPA removal is really necessary.
JPA is converted to ‘new-super-persistence-api’ in this manner.
Of course, there are many points to modify the DB module’s code.
However, the interfaces that the ‘DB module’ offers won’t change.
It means that only internal implementation has changed.
The ‘Payments API module’ is still not dependent on any database access method after the modification.
Additionally, you won’t always need to be aware of the technology you’re using, and you may perform your own business logic without changing the code.
Therefore, by using this structure, it is possible to retain a state that does not alter or have an impact on the higher level module, even if the lower level module is modified.
It is reasonably simple and fast to make changes in module units with the least effect on the current company when a new technology is introduced or when technology has to be changed in the future.
So far was my experience of module’s development and expansion that I consider and work toward for sustainable software.
We’ve studied every topic, so why do you think I am sticking to these principles and ideas?
It’s because I’m basing my thoughts on two crucial keywords to develop sustainable software.
Management is the first. I want total command over the program.
The largest benefit to the company and to myself is always provided by this.
The first hidden keyword in this post was Management.
To explain this, Business-Logic, Layer, and Module were classified into topics and studied.
I depict the business logic, establish boundaries, solidify the layers, and isolate the technology with the proper modularity.
As a result, it is crucial to develop organic software in a controlled environment that can adapt to varied changes.
The second hidden keyword was Control.
I want to make the software predictable and controlled based on this control.
Depending on the company or project, it could differ, but I think that the software we develop should essentially be able to run while expanding continually.
The software we’re developing might last for a very long time.
Since you can never predict when or how software may develop or degrade, I believe you should always be ready to develop.
I always believe that you should be able to Management the software and have Control over it in order to do that.