The architecture is the “what“, while the design is the “how“. The architecture defines what the system does while the design defines how the system does it. The architecture is the blueprint, the documentation, while the design is the implementation abstraction, the code being the implementation.
From a different perspective the architecture is on a higher level of abstraction compared to the design, and after design comes the code.
A good architecture leads to a good design which leads to good code.
This discussion is about the design of a software system in the context of C4 architecture modeling but also about design considerations in general and of course lessons learned.
Because design comes after architecture and because a good context is appropriate to better understand a certain topic, I recommend you first take a look at The C4 Software Architecture Model in Action and the example application FoodTavern to define an appropriate context for the discussion.
The example application
FoodTavern (the provided software system) is an 5-tier full stack system based on clean architecture:
The system is composed of the following C4 containers (to understand what containers are, see The C4 Software Architecture Model in Action):
- Android client application
- Desktop client application
- Web client application
- RESTfull web service
The presentation layers are represented by the Android, Desktop and Web applications. Each presentation layer is implemented as an asynchronous Model View Presenter (MVP) design:
The API layer, Business layer, Data layer and Database layer are implemented at package level by the RESTfull web service.
If you want to see a Maven module approach to clean architecture, based on the same system, see: FoodTavern – v2.0.
Having already defined the architecture it is time to move on to the design phase of the system.
The following points were considered for the design phase:
- A single GIT repository
- Folders/packages structure
- Design patterns
- IDE agnostic
- Loose coupling
- High cohesion
- DRY (don’t repeat yourself) principal
So the first question was how to package the system’s containers in a consistent way, all bundled together in a single GIT repository.
Well first I have created a folder named FoodTavern that represents the whole system. Each container has its own sub folder under the system folder.
- android-application [Container]
- desktop-application [Container]
- web-application [Container]
- web-service [Container]
Next comes the question of how to further structure the packages/folders to achieve consistency of code location across containers. What this means is that classes with the same purpose, like the MVP implementation, are located in similar locations in all containers. This makes the process of understanding the system a breeze because everything is where you expect it to be.
Enter the software project management tool Maven. It has its own default way of handling code and resources thus achieving some consistency across containers, meaning that code and resources are in the same place in all containers.
Please note that the android-application container uses Gradle as a build system because of Android Studio. As a best practice, the whole system should use the same software project management tool e.g. Ant, Maven Gradle. Nevertheless, Gradle is in some ways similar to Maven and for such a small system this is not a big issue.
It also worth saying that by using a build system like Maven or Gradle you also get an IDE agnostic software project, which can also be handled by a continues integration tool.
This code and resources consistency is a great start, however, it is not enough. To further achieve consistency we have to carefully consider the folders/packages structure of each container.
It came as a pleasant surprise to notice how nicely the packages/folders structure was influenced by the architecture modeling with C4.
So let us see how to decide the packages/folders structure further.
As best practice always start with a reverse domain, like ro.ovidiuconeac, as the root of your code. This applies to all containers.
To which you add a product name ro.ovidiuconeac.foodtavern and you get your system.
So from a C4 perspective, following the steps, you have:
- ro.ovidiuconeac.foodtavern.webapp.components.serverconnection (ignore for the sake of consistency)
- ro.ovidiuconeac.foodtavern.webapp.components.navigation (ignore for the sake of consistency)
- ro.ovidiuconeac.foodtavern.webservice.components.serverconnection (ignore for the sake of consistency)
- More about classes later.
Let’s also see some images with the actual code structure from the FoodTavern system, more precisely the components of each container.
While designing the folders/packages structure for each container, technologies choices had to also be considered in each case. Sometimes a certain technology has a certain way of doing things which might not be consistent with other technologies. The simplest way it to stick to the default approach of each technology, and also you will often hear people say that this is always the way to do it.
My opinion is that you first must consider the best practices of the selected technologies, understand the reasons behind the mindset of each technology, then decide as a team if the “default” works for you or if a new approach must be considered.
What I have strived to archive with FoodTavern was the second option, a new approach while still considering the particularities of each technology choice and the best practices in software development.
Because I like the way that Android projects are structured, I have attempted to implement as much as possible from this structure in all presentation layer applications: android-application, desktop-application, web-application. To put is simply, they handle UI resources and code in a similar way. Why similar and not identical? Because technology choices influence the way of how the product is built, deployed, developed, etc. So some technology imposed constraints simply cannot be overcome without making a big mess.
This means, for example, that you will always find the UI layout resources in a resources/layout or res/layout and the actual code in a folder/package structure like ro.ovidiuconeac.foodtavern.components.*
Great! We have achieved a new level of detail in the design. So what comes next?
Carefully consider what you want to achieve and ask yourself, is this a new concept, has anybody done it before? Enter design patterns.
What we want to achieve first, are presentation layers based on the MVP (model view presenter UI design pattern). An MVP design pattern encourages loose coupling and high cohesion for the components that handle the user interfaces of the presentation layer applications: android-application, desktop-application, web-application.
So each presentation layer application has an MVP component called Food. This is pretty much the only generic design pattern used besides a singleton design pattern for a utility class.
The components of the web-service containers are implemented based on layers from top to bottom:
- API (RESTful endpoints)
- business (the domain logic)
- data (repository design pattern for data acces)
Because the web-service container is implemented using SpringBoot, to ease development a repository pattern was considered for accessing data from the in-memory database.
The class diagram(s).
Now we are finally getting to the actual code. It is time to see the UML components diagrams for all containers.
If you want to have an even more clear picture of the code design, you can create a diagram for every component. But for the moment is enough if you get the big idea.
Implementation and design consideration
Finally, it is time to implement the system. You already have all the necessary information, so this step is just translating the design into code.
During actual development consider all OOP principals like:
The SOLID principals:
- Single responsibility principle
- Open/closed principle
- Liskov substitution principle (design by contract)
- Interface segregation principle
- Dependency inversion principle
Clean code principals:
- From the Clean Code Book by Robert Cecil Martin
Also consider other schools of though in software engineering.
It takes time to plan a project, but once you have a clear idea of what and how you want to do in/a software project, the actual implementation phase almost becomes a routine.
Take enough time to plan the design, it will be well worth it. Without a good design plan you will refactor and refactor until you get a clear picture in your mind of what you want to achieve, and then you refactor again to achieve that goal. You will probably get the same result but with a high cost of, from personal experience, three times as much development time.
It is amazing to see how a good architecture can lead to a good design, which in turn can lead to good code.
There are a plethora of things to consider when doing a complete software project. It takes time to learn all of this stuff, it takes time to understand how to fit everything together, it takes time to easily navigate a project from overview to details and vice versa. Do not get overwhelmed, remember that the more time you invest in something the better you will understand it.
Also consider everything described here as a school of thought. The field of Information Technology is still young and we still struggle to find a common language between software engineers.
Because I was developing the project on a Linux machine, I was unable to build it with the mvn clean install command. This was due to OpenJDK and its support for JavaFX, so all I had to do was to tell the OS to use an Oracle JDK instead.
The android client application uses Gradle as a build system, which can use Maven dependencies. This has helped me in sharing some code in the whole system, e.g. the models, located in the common Maven module.
You can design the architecture either at package level or module level. It depends on the context of your application. It you have a small system, I would suggest package level design for clean architecture. For complex projects module is the answer.