Designing a big application can be challenging and not an easy task to do, as requirements adding up, we expect a lot from big applications such as availability, fault tolerance, scalability… But all of this seems in favor of the end user perspective, but what about us? Do we need something special for that?
Well if you can say “it’s a beautiful interface” or “such a user-friendly tool”, i can say the same thing applies for a code base, there is nothing better than someone new taking a look at your code and says “Woooow!!! It’s wonderful” or “This code is very extensible and flexible”.
Designing and building applications is an art, it is something that cannot be taken as granted because of the numerous critical decisions that must be made throughout the whole process. But i should share with you my current knowledge about so let’s start.
This is the most important step in the whole process, as you must analyze precisely the requirements and choose the most appropriate architecture, because there is no perfect choice, we always have to make a compromise. In this step, foreseeing the future is desired as sometimes requirements lack vision and tend to be limited to the current timeline, but this depends on the nature of the project.
Opinions vary as to the scope of software architectures:
- Overall, macroscopic system structure; this refers to architecture as a higher level abstraction of a software system that consists of a collection of computational components together with connectors that describe the interaction between these components.
- The important stuff — whatever that is; this refers to the fact that software architects should concern themselves with those decisions that have high impact on the system and its stakeholders.
- That which is fundamental to understanding a system in its environment”
- Things that people perceive as hard to change; since designing the architecture takes place at the beginning of a software system’s life-cycle, the architect should focus on decisions that “have to” be right the first time. Following this line of thought, architectural design issues may become non-architectural once their irreversibility can be overcome.
- A set of architectural design decisions; software architecture should not be considered merely a set of models or structures, but should include the decisions that lead to these particular structures, and the rationale behind them. This insight has led to substantial research into software architecture knowledge management.
- There is no sharp distinction between software architecture versus design and requirements engineering. They are all part of a “chain of intentionality” from high-level intentions to low-level details.
Architectural patterns and styles
Choosing between architectural patterns can be a tricky process as there are plenty of them out there, an architect must be careful and must pay attention to the cost, performance and sustainability. You can find here an article talking about some software architectural patterns.
Here is the list of some architectural styles and patterns:
- Client-server (2-tier, 3-tier, n-tier, cloud computing exhibit this style)
- Event-driven(or implicit invocation)
- Layered (or multilayered architecture)
- Microservices architecture
- Monolithic application
- Model-view-controller (MVC)
- Peer-to-peer (P2P)
- Pipes and filters
- Reactive architecture
- Representational state transfer (REST)
- Shared nothing architecture
- Space-based architecture
Some impediments to avoid
- Lack of adequate architectural talent and/or experience
- Insufficient time spent on architectural design and analysis
- Failure to identify the quality drivers and design for them
- Failure to properly document and communicate the architecture
- Failure to evaluate the architecture beyond the mandatory government review
- Failure to understand that standards are not a substitute for a software architecture Failure to ensure that the architecture directs the implementation
- Failure to evolve the architecture and maintain documentation that is current
- Failure to understand that a software architecture does not come free with COTS or with the C4ISR Framework
As much as architecture and design are important, i think the code is the most fundamental thing in the process. Its like when building a house, you must use good materials which can sustain you longer, unlike the architecture which looks more into organization and making the system more extensible
Achieving the ultimate level of code quality is achievable with simple steps and by following a certain set of rules which i will be enumerating later.
Building a big thing efficiently, requires the use of robust components. Going big is not something that you can succeed on doing if you are willing to stick to the traditional methods and tools. The only thing that will always stand between you and that big thing is “Looking to what is in your field of sight”.
The success factor of every big system is reusable components, why? because an update can be easily propagated, because it respects the single responsibility principles and because building is meant to be like that.
So the thing that i advise you to do, build reusable components, think of your system as a stack, make your own set of tools on top of a backed up frameworks.
Use stable external libraries
Well, you’ll end up using a list of libraries and as your product grows, the list of these libraries will grow and your system will rely on them. So keeping your system healthy and safe requires you to keep track of your used libraries, make sure it is legal and that they are supported by a community.
Bugs are something that no system can be free of, we need to know what happened in case of a bug, a failure or an anomaly. Think of it like a black box of a plane.
Logging is very important and needs to be integrated in every corner of your system. In a large system, centralized logging sinks and servers are very useful and are easy to use. If you don’t want to use a logging server, just use a logging library that supports any DBMS you desire.
An example logging server that can come in handy is Datalust Seq.
In a distributed system, tracing requests and queries is very important to identify performance issues and to determine the correctness of the path of a specific chunk of data. As components number and interactions increases with the size of the system, you need to put in place a tracing capability. You can take a look at Jaeger.
SOLID is an acronym for 5 important design principles when doing OOP (Object Oriented Programming). Each letter stands for a principle and are defined as follow:
- S: Single-responsibility Principle
- O: Open-closed Principle
- L: Liskov substitution principle
- I: Interface segregation principle
- D: Dependency Inversion principle
Reduce duplication by using design patterns, refactor your code and opt for a better architecture. Duplication reduction is achieved by adopting all the measures i mentioned above, therefore, it is important to note, that a code-base that is full of duplication will be very difficult to maintain.
Document, document and document, you might think why i did repeat it three times. Well the first one is for the basic code documentation which includes (summaries, purpose description, code annotation, algorithm steps description). The second one is for the developer documentation, one that is crucial if you want to scale your team and make sure that the building machinery is standard and robust, this is done by using an internal wiki that includes (tutorials, how to do, the components designs, diagrams, technical references and papers…). Finally the third type of documentation is API docs, well you want your system to be reusable and if you are willing to expose some services, you need to document those interfaces so that any third party developer or even one of your coworkers in different team (like front end) can easily use it.
The user guide is something that is taken for granted that’s why i am not mentioning it.
Test and automate tests. This is very important, i do understand that an MVP or a small scale product can skip this part due to the tight implementation schedules. But i prefer not to, why? because if we keep convincing ourselves that this is valid, we will probably wake up until its too late.
Testing is crucial and it is not simple, there are various types of tests and each one of them have a purpose. You might have heard of Unit testing, integration testing, end to end testing… Let me explain these most important three:
- End to End: A helper robot that behaves like a user to click around the app and verify that it functions correctly. Sometimes called “functional testing” or E2E.
- Integration: Verify that several units work together in harmony.
- Unit: Verify that individual, isolated parts work as expected.
To find the right balance between all three test types, the best visual aid to use is the testing pyramid. Here is a simplified version of the testing pyramid
The bulk of your tests are unit tests at the bottom of the pyramid. As you move up the pyramid, your tests gets larger, but at the same time the number of tests (the width of your pyramid) gets smaller.
Writing a beautiful and clean code requires the respect of certain guidelines and conventions. It is recommended to use use a standard naming convention, usually the one that is used by the programming language or target framework you are using. But we always tend to make mistakes, that’s why we need tools to keep us aware of those mistakes and to point us to the right direction.
Semantic and syntax analysis
The first fundamental thing is to use a tool that supports semantic and syntax analysis of your code. You can’t use notepad or gedit to write your code and expect it to be error free. We all end up making those errors and the last thing we want is to have a failed build due to syntax errors.
Using an IDE is very important, it makes us very efficient especially when they include smart indexing features that will allow fast and intelligent code auto completion. An Integrated Development Environment, reduces the risk of shipping a non-build-able code almost by 99% and allow you to be very very efficient. Because it knows what code you are typing and what does it mean, so it can point you to the the closest direction in which you wanna go in.
Code Cops/Refactoring tools
Think of it like cops and rehab facilities, well the example might be very extreme, but somehow it looks like that. The code cops makes sure, that you are not violating conventions and rules, it even alerts you in case the code have the potential to contain a bug (e.g: using a reference that can be null).
Use code scanners for teams, as your team grows, you can’t rely on individual code cops and refactoring tools, therefore, you must put in place a centralized code analysis tool that will make sure that everyone is following the rules and no one is violating the conventions. These tools also helps reduce the risk of having bugs, vulnerabilities and even helps detect code coverage.
An example tool that will help you explore this topic further is SonarQube.
Git flow/Peer review
Peer review is very important, we must always have a second opinion, because we humans lack the ability to see everything from a single perspective. Peer review is as important as having two eyes instead of one eye.
Use Git Flow to control features development and to introduce a robust peer review system before merging. This also can be pushed further by adding quality control builds and automated PR decoration.
Finally, i recommend using continuous integration (CI). This is very important if you are willing to automate testing and make sure that the integrated code is build-able and passes all the regressions tests.
Well i hoped i could cover as much as possible, but eventually, designing a beautiful code-base is not something that is achieved using one or two tools or principles but it is a combination of many set of principles and tools. I suggest you explore further each individual topic using our friend Google.