Skip to main content

Software Architecture

· 19 min read
Bruno Carneiro
Fundador da @TautornTech

Software Architecture

Infrastructure for Credit](https://cdn-images-1.medium.com/max/2000/1*gAvcIkpA_3emvVIcLEq6tQ.png)

When we talk about Software Architecture it is common to reference the main aspects of the design of a software system. Architecture is something very important and has the power to make a project/company a success or a great failure, depending on how it was planned and executed.

In this article I will discuss Software Architecture, its importance, risks, and what impact it has on your product/service/business.

https://www.monkeyuser.com/

What is Software Architecture?

Software architecture can be very broad; it can be defined as the foundations of how a software was developed, the separation of its components, and how they interact with each other. For many software teams and companies this can have other meanings.

I like to define software architecture as the separation of small components that interact with each other, creating an application robust enough to solve certain problems.

Think of it this way: several functions, each with its own responsibility, that separately may not make much sense, but when united will create an application intelligent enough to perform currency transfers between different nationalities.

Of course, thinking about it this way seems very simple, but for this to become simple, robust, scalable, and reliable, a very well-crafted design is necessary, thinking about each problem the software may present and how it will behave under use by its customers — because an excellent architecture is useless if there are no customers using the application.

What is NOT Software Architecture?

But before advancing in the understanding of Software Architecture I need to make clear what it is not. Software Architecture is not a set of folders — that is just the organization of a system. Of course a good architecture will only work if there is good organization, but the folder structure does not define a good architecture.

As in the example below:

Part of a folder structure for a Front-end project using ReactJS

Why is Software Architecture Important?

  • A unified vision: Architecture provides a single vision of communication across all stakeholders. In this way, the entire team works on the same vision, speaking the same "language." Regardless of whether they are developers, QA, managers, etc., the team here will have a common vision.

  • Solid foundations: The initial decisions for software development are the most important. At this point the base structures and patterns are elaborated/defined with the whole team and at this stage can easily be modified — drastically reducing the cost of future changes. Think of this point as the structural foundation of a house: if it is not well thought out it can cause major future problems, even the ruin of the house.

  • Scalability: Regardless of the size of the project it needs to scale to an audience X — thinking of course that we want the software to prosper. The more users, the greater the load on the system, and thus its components need to be very well thought out and executed so that the software can scale in an "easy" and gradual way. A clean architecture with good foundations can make development, monitoring, and evolution much easier, as well as maintenance/bug fixes that will always exist.

  • Improved delivery: Whether it's a software product, app, site, blog or whatever, there will always be the delivery of a new version. And good architecture will help ensure that these new versions made available to users do not cause problems and can be done transparently for users.

  • Reusability: The design of architecture components can make reuse easy or chaotic. Depending on how it was designed, code generation can be so large and poorly crafted that it makes the reuse of something unfeasible or inadvisable. That is why reusability must be very well thought out. The larger the development, the greater the cost and time involved, and this can be a bottleneck in the evolution of the software.

Delivery Delays

When we have software with many crufts this will increase the time for development and will directly impact the quality of the software, and without a doubt make the customer dissatisfied due to delays (which will occur) in the delivery of new features. And worse than that are inconsistencies between how the software currently is and how it should be.

Here is an image taken from Martin Fowler's website where he describes crufts (basically problems in the system) in the delivery of a feature.

The greater the number of crufts, the more expensive the evolution of the system and the availability of new features becomes.

A great differentiator here is that we can separate development teams into two categories:

1 — With many crufts in the system

2 — With some crufts in the system

There will always be technical debts and incorrect behaviors; the real difference is the quantity and how this affects the user.

Below is a graph representing the cost of the life cycle.

Note that the less is invested in detailing your design, production, definitions, and software planning, the faster it can start, and for a brief moment it may even seem that things are going well and even quickly — but as time passes, the cost of changes to the design will become very expensive, and new features will become increasingly slower, often reaching a system with only maintenance mode due to the difficulty of making changes.

The opposite is true: the greater the initial investment in planning, architecture, design concepts, and detailing, the higher the initial cost will be, but maintenance and evolution will be much faster, more efficient, and reliable.

Read more at: https://martinfowler.com/articles/is-quality-worth-cost.html

High quality software is cheaper to produce

Common Software Architecture Patterns

A good architecture needs to use development patterns and primarily identify which architecture is most appropriate for your problem/business.

There are N ways to develop a system today, as well as its architectures, but always think of the simplest one that will actually solve your problem, not the coolest one of the moment.

Below I list some patterns that are common for the development of a software architecture.

Microservices

This architecture pattern aims at creating services that function independently. It is very common to have a software monolith (basically interconnected services where if a problem occurs at any point in the application everything stops working), and microservices advocate exactly the separation into layers so that each entity functions in isolation. In this way, if a problem occurs in one of the services, the others will continue to operate normally.

Imagine that a login service has a problem: users who are logged into an e-commerce system can continue browsing normally, even making purchases. If it were a monolith, this would not be the case.

With this separation into layers we can separate software development/tests etc. into multiple teams/journeys.

https://www.redhat.com/architect/5-essential-patterns-software-architecture#microservices

Event Sourcing

In this pattern, components are completely decoupled and each component has its own form of identification, processes, responsibilities, and events.

Basically there is a component A that fires an event where there will be one or more components that are listening to component A's event.

Imagine that a customer component fires a customer:created event and with that another module listening to this change can perform some process/call, such as sending an email.

This architecture commonly has mediators that produce the event and generate its signature so that other points in the system can consume it.

Architecture commonly used in applications and allows high scalability.

https://www.redhat.com/architect/14-software-architecture-patterns

Layers Pattern

Probably one of the most used patterns in software development. This pattern basically divides the code into layers where each layer has its own responsibility and makes service calls to the next layers.

In this format we have structured components that will be responsible for the next calls, creating a continuous flow of rules.

https://www.redhat.com/architect/14-software-architecture-patterns

Micro Front-end

https://martinfowler.com/articles/micro-frontends.html

The term Micro Frontends first appeared in ThoughtWorks Technology Radar which concluded in 2016. It extends the concepts of micro services to the frontend layer. The current trend is to build a rich and powerful feature application for browsers, also known as a "single page app," which sits on top of a micro services architecture. Over time the frontend layer, often developed by a separate team, grows and becomes increasingly difficult to maintain. This is what we call the Frontend Monolith.

The idea behind Micro Frontends is to think of a website or web application as a composition of features that are owned by independent teams. Each team has a distinct business area or mission which they care about and specialize in. A team is cross-functional and develops their features end-to-end, from the database to the user interface.

Reference: https://tautorn.github.io/micro-frontends/

MVC

https://www.redhat.com/architect/14-software-architecture-patterns

A pattern that divides the application into 3 components or layers. The model contains application data and main functionality. The view presents data and user interactions (the screen the user sees). The controller mediates the model and the view according to user interactions. This pattern allows the use of multiple views but the abstraction of layers increases complexity and makes scaling difficult.

Serverless

https://www.ideamotive.co/blog/software-architecture-design-best-practices-you-should-know

Architecture using serverless is a cloud development model where it is not necessary to create and configure servers. This is a cloud computing pattern where the service provider is responsible for managing the infrastructure (such as AWS, GCP, Azure) and for scaling applications. Applications are deployed in containers that are started on demand, according to the usage of the created functions. This model is commonly called IaaS (Infrastructure as a Service).

In simple terms, serverless architecture is a solution that allows the creation of an application where developers can create efficient cloud environments with all the computational resources needed in their development process, without worrying about infrastructure and machine management/scaling.

This is the pattern used by Base39; almost 100% of our services today use Serverless architecture, allowing the team to focus on application development.

Of course, in this pattern development patterns are needed, such as Clean Architecture, DDD, or another pattern that best fits the challenges of the project.

There are countless other development/architecture patterns; I listed some common ones above.

Visualize Your Design

Before starting to code it is important that the team has the design in mind: sketch it out, think about functional and non-functional requirements, create prototypes, data models, etc. This approach is much cheaper and provides a clearer view of how the architecture will work and how each component will interact and communicate with the others.

This way it is possible to avoid architectural gaps and work with what is actually needed to start the project, always thinking about scale and future evolutions.

Best Practices for Software Design

It is quite difficult to determine what good architecture is because there are several ways to solve the same problem, and it is possible to apply various architectures — of course depending on the path it may be more laborious and have a higher cost.

Either way, to develop good architecture it is always good to consider some best practices, and I always like to ask myself some "questions" to identify if I'm on the right track, such as:

  • The software needs to be easy to maintain;

  • It needs to be scalable and reliable;

  • Easy to refactor;

  • Easy to adapt to requirements that arise throughout development and after the application is delivered;

  • Have high performance and fast execution;

  • Actually work as it was conceived;

  • The architecture must be resilient and easy to find and fix bugs;

  • The team needs to have mastery of the technologies used;

  • The code should not have repetitions;

  • The architecture must respond well to changes without performance and quality degrading.

Simply looking at the product/project will not make it possible to identify good architecture, but looking at how the product works it is possible to capture signals of how things were made. These "signals" are some of the items listed above.

Problems That Poor Decisions Can Lead To

https://www.monkeyuser.com/

There are several errors that are common in the development of a software architecture, and this can be the ruin of the project/product and even the company!

Depending on how things were conceived, developed, and evolved, software maintenance can be a very critical factor.

Throughout my career I have witnessed countless projects that were incredibly expensive from a cost/benefit/time perspective — a simple change or fix took days. And this is quite common as the project evolves, due to conceptual flaws and day-to-day rush and pressure to "deliver value to the user," meaning good practices and architectural patterns can simply be ignored or removed. Below I listed some common mistakes:

  • Incorrectly defining the problem;

  • Lack of documentation;

  • Not thinking about the scale of the application (here you don't need to think about what the project will look like in 10 years — it may not even exist anymore — but it is necessary to understand the scale level and think about how its future behavior will be);

  • Not getting early feedback: Many projects take a long time to go to production; the team spends months developing and when it goes to production it ends up frustrating the user. This is also very common due to requirements gathering and project planning — not just software. Very often the team as a whole believes they should launch a big feature and spends a lot of effort validating whether it will actually have an impact on the user.

  • Excessive complication: This is one of the most common examples. Adding many technologies, architectural patterns, "best practices," complex terms, and much more — all of this may seem like quite robust software, but it only tries to hide a lack of knowledge and purpose. Making something complex does not make it good, only complex. Always opt for keeping everything as simple as possible; software that scales is something simple within the limits of its needs and standards. Save that new technology for testing at home :)

  • Not keeping technologies up to date: Many teams focus only on new demands, features, etc. and end up letting technologies become outdated, causing a major future problem. This item is like a sink full of dirty dishes: the team can keep postponing, but one fine day they will have to stop and wash the dishes, because it is no longer possible to evolve the project and bugs are appearing everywhere. This can also lead to security risks.

There are countless other common mistakes but I wanted to highlight some that are notable.

Use Case Using Edge API

At Base39 we have numerous projects and applications, and we had a problem of sharing services between applications where specific rules were generated for each system, making the logic, security, and maintenance difficult to manage. The evolution of the APIs was also a problem, since a simple change in a service for one application could generate a side effect on another independent system.

To solve these problems we created Edge API or Back for Front (BFF) layers at Base39 to address the problem of developing APIs directly tied to the front-end, thus allowing each application to have its own rules.

The Edge APIs were created so that it is possible to develop a BFF layer where all the rules the application needs can be developed in it without the need to create new APIs that return contracts according to their needs.

With the development of new APIs, the need for a BFF arose where each application can develop/use its own customized layer, thus having its own business rules without the risk of breaking contracts of existing APIs. The consumption of each API is through HTTP methods that are called by the BFF which contains all the types and resolvers to transform the data returned by the APIs.

For this, AWS resources were used, such as: Lambda and AppSync. These tools are responsible for creating and making available a service with GraphQL to return the contract that the application needs, without the need to provide more or less information than necessary.

Architectural Design:

There are 4 layers for the application to operate as expected:

1 — Client layer, containing all applications that access Base39 platforms via mobile devices or Web applications;

2 — BFF layer, responsible for creating types and resolvers and HTTP calls in a configured API Gateway;

3 — API Gateway with the route map config;

4 — Availability of services such as EC2, Lambda, Dynamo, Mongo, etc.

The flow below represents the creation of n BFFs to represent each client, where they can coexist with their own rules.

For clients working with the SaaS format, only one BFF can be created where it will provide all the infrastructure and rules necessary for its development and operation. For clients who will operate with their own BFF, it is worth noting that the return layer in each BFF should be done according to their application. If this is provided by Base39 it must be done equivalently among all SaaS clients.

If it is an Enterprise client and they want us to provide a dedicated BFF, this can be a mirror of an existing BFF or simply a new application should be created returning a unique contract for the client to use in their scope, but it must be the single controller of this application, which may or may not be changed only if the client requests it, regardless of the evolution of Base39 products that can coexist with applications and API contracts equal to each other, but with a unique resolver layer if this is necessary.

On Distribution

All BFF development continues using AppSync, which provides a GraphQL endpoint containing all queries, mutations, and resolvers for the application. The publication of resources on AWS is done through the CDK.

Distributed APIs

The APIs that this project uses were made available on the Developer Portal of Base39.

These APIs have their own rules and layers of security/access control and governance, enabling the client to operate them through an API key that authorizes actions on the services.

On the Database

Each SaaS client operates with their own data cluster, thus creating a physical separation through a database that is hosted in a Cloud accessed through the Multi-Tenant platform provided by Base39.

"If you think good architecture is expensive, try bad architecture" — Brian Foote and Joseph Yoder's

Above is a piece of the architectural design and how the BFF layers solved a communication problem between distinct applications making calls on common APIs.

Conclusion

The development of good software architecture is not easy, and this is the foundation of any software application. As I said earlier, poor choices can lead the project to ruin. Therefore, one must be very careful with each architecture, each project, and the time to develop it — always take into account estimates, deliveries, alignment with stakeholders, the team, and how the architecture will allow good operation of the application and enable the resolution of a particular problem.

This is a very broad subject and I have listed here only some examples, best practices, and a case developed at Base39.

Always spend time planning and thinking about how the architecture will work, always try to keep it as simple as possible, and don't fall into the trap of delivering something quickly without thinking it through, believing that technical debts can be easily resolved in the future. Good software architecture is cheaper than poor choices.

References