Once again, I stumbled upon the documenting architecture decisions post by Michael Nygard. This time, a particular line from his post got me thinking and made me dig a little deeper into the subject. The line said, “ ‘architecturally significant’ decisions [are] those that affect the structure, non-functional characteristics, dependencies, interfaces, or construction techniques.” The reason this piece of statement is important is because most of the time architects are not clear about what they should document.
The author suggests that we must document architecturally significant decisions. He divides them into five categories. In the post, he does not give examples against those categories, so, I am taking the liberty to add mine.
Below is my interpretation of these categories. It could be different from the author’s intent.
My understanding is that these are the decisions that have an impact on the architecture style and code organization pattern being used by the system. Examples of such decisions are:
- In Microservices architecture, for service-to-service communication whether you use a synchronous or asynchronous mechanism.
- In Serverless architecture, the decision to connect different functions using a central orchestrator like step function or in a more decentralized choreography mode using stream and events.
- Using a shared database in a Microservice architecture.
- Using service mesh instead of Spring Cloud Netflix(in general a library-based approach) for resiliency and communication.
- Use of monorepo over polyrepo(repo per service/component).
- Use of layered or hexagonal architecture (code organization).
Non-functional Characteristics(or NFRs)
All architects know about these. But, how many of us document them or even get the right information from the customer is a different story.
Non-functional characteristics are the characteristics that help us judge the operation and management of a system. Examples of such decisions are:
- Configurability: This could mean multiple things and you need to document each of them
- We should be able to configure the system without making any code changes.
- Configuration should be stored in an external configuration store and changes to configuration should be dynamically reloaded by the system.
- Server driven UI. The metadata for the UI should be fed from a backend.
- And many more.
- Performance: Example like
- Adding a cache to improve performance of the application.
- Using denormalized database schema in RDBMS over normalized database schema.
- Rewriting a specific service in a specific language for performance reasons.
- And many more.
- Availability over consistency or vice versa. Depending on your system you might choose one or the other. Therefore, documenting it becomes important.
- Security. Examples like
- Encrypting request payload.
- Using jti in JWT token to protect against token replay attack.
- Using a WAF.
- Using a mobile app security SDK.
- And many more.
You get the idea.
These are the decisions that impact the coupling between different components and services within the system. The examples that fall under this category are:
- Whether you use synchronous or asynchronous means for communication between components/services will have an impact on the degree of coupling.
- Adding an anti-corruption layer in your system. Anti-corruption layer prevents a downstream system/service domain model from polluting the domain model of a new service.
- Merging two Microservices to one, to reduce their communication overhead.
- Using service discovery instead of hard coding URLs is another example.
These are the decisions that involve which API style to use, how to expose APIs, how to document APIs, how others can consume your APIs, etc. The examples under this category are:
- Choosing either or combination of REST, GraphQL, or gRPC:
- If the API is used by external users then use REST;
- If the API connect well together and you need to build API aggregation layer then use GraphQL;
- For internal service to service communication use gRPC.
- Using OpenAPI specification for documentation.
- Using URL based version strategy for APIs.
- Choosing a strategy for backward compatibility of public APIs. We might decide that we will never delete any field, we will only add new fields.
- Using a centralized API manager for exposing and discoverability of APIs.
- Using contract first development.
- Using a mock server with fake data for the UI team to work independently, while the backend team develops the API.
These are the decisions that refer to tools, frameworks, deployment platform, and development process that the team will follow during software development. The examples under this category are:
- Using Spring Boot with Reactor to develop all Microservices.
- Using AWS EKS with Istio for platform.
- Using Github pull request based development process. Each pull request will require approval from two reviewers.
- All developers should use standard tools for software development. This ensures better pairing and tools knowledge transferable.