DEV Community

Cover image for ๐Ÿ‘ฎ Architecture Governance: Keeping Your System's Design on Track with Continuous Arch Testing
๐Ÿง‘โ€๐Ÿ’ป Kamil Bฤ…czek
๐Ÿง‘โ€๐Ÿ’ป Kamil Bฤ…czek

Posted on โ€ข Edited on โ€ข Originally published at artofsoftwaredesign.net

2

๐Ÿ‘ฎ Architecture Governance: Keeping Your System's Design on Track with Continuous Arch Testing

๐Ÿ“š Tl;dr

  • Architecture governance is a system for maintaining consistency and quality in architectural decisions throughout the development process of a software project.

  • It involves having control over architectural rules and guidelines and ensuring they are adhered to.

  • Fitness function is an important component of architecture governance which acts as a "cop" to ensure developers don't break established architectural rules and measures the alignment of the software architecture with business goals.

  • NetArchTest is a tool that can be used to implement the automatic fitness function by writing unit tests that check dependencies between packages, classes, and methods in the codebase.

๐Ÿ“ What is architecture governance?

When building software projects, there are many architectural decisions that need to be made. It is important to maintain consistency in these decisions to ensure the overall quality and maintainability of the project.

Having a system for architecture governance allows the team to have control over the architectural rules and guidelines, and to ensure that they are being adhered to throughout the development process. This can help to ensure that the project remains on track, and that any issues or inconsistencies are identified and addressed in a timely manner.

Additionally, having a well-defined architecture governance system can facilitate collaboration and communication among team members and help to ensure that everyone is working towards the same goals. This can lead to a more cohesive team and a better end product.

It's also important to note that architecture governance is not a one time process, but a continuous one that adapts to the changes of the project and the organization. That way, the team can ensure that the software architecture is aligned with the business goals and the evolving requirements.

๐Ÿ‘ฎ Fitness function

Fitness function is a way to measure the alignment between the software architecture and the business goals.

One approach to implementing a fitness function is through the use of automatic fitness functions. Automatic fitness function will be in our case units that will check the validity of architecture decisions.

๐Ÿš€ NetArchTest comes to play

NetArchTest is a library that can be used to write such tests (automatic fitness functions), it allows to check the architecture and ensure that it adheres to predefined rules and constraints.

With NetArchTest, developers can write unit tests that check the dependencies between different packages, classes, and methods in their codebase. This can be useful for checking that the architecture adheres to principles such as separation of concerns and the single responsibility principle. By running these tests regularly, developers can ensure that the architecture remains consistent and that any changes to the codebase do not violate the predefined rules. ADR and Arch unit test is amazing mix that will help you keep consistant great design.

๐Ÿ“ Example 1๏ธโƒฃ

ADR 1: Creating a Custom Shared Logging NuGet Package for Improved Performance and Maintainability.

Status

Approved

Context

We have identified that the current using the Microsoft ILogger interface does not provide optimal memory efficiency and there is inconsistent code optimization across our solution. This can lead to performance issues within our codebase. Additionally, having a consistent logging implementation across the solution will make it more maintainable and easier to understand.

Decision

We have decided to develop a custom shared logging NuGet package with โ€œIOptimizedLoggingโ€ interface to improve the efficiency of our logging system.

Consequences

๐Ÿ‘ Pros:

  • Improved memory efficiency: The custom shared logging NuGet package will improve the memory efficiency of the logging system, as the optimized implementation will be used consistently throughout the solution.
  • Improved performance: The package will also improve the performance of the logging system, as the code that optimizes log execution will be consistently implemented throughout the solution.

๐Ÿ‘Ž Cons:

  • Increased development and maintenance effort: The development of a custom shared logging NuGet package will require additional resources and effort, including the development and testing of the package, as well as its ongoing maintenance and updates.
  • Each new developer or current one has to know that he must use IOptimizedLogger instead ILogger

๐Ÿ‘ฎ Fitness Function

private readonly IEnumerable<Assembly> _solution = AssemblyLoader.GetSolution();
[Theory]
[InlineData("Microsoft.Extensions.Logging", "Divstack.Estimation.Tool.Shared.Logging.OptimizedLogger")]
public void GIVEN_validate_solution_THEN_should_be_no_microsoft_logger_use_excluding_optimized_logger_package(string forbiddenLoggerNamespace, string allowedLoggerUseNamespace)
{
var policy = Types.InAssemblies(_solution)!
.That()!
.AreClasses()!
.And()!
.DoNotResideInNamespace(allowedLoggerUseNamespace)!
.Should()!
.NotHaveDependencyOn(forbiddenLoggerNamespace);
var result = policy!.GetResult();
result!.FailingTypeNames!.Should()!.BeNullOrEmpty();
result.IsSuccessful.Should()!.BeTrue();
}

๐Ÿ“ Example 2๏ธโƒฃ

ADR 2: Notifications module should be decupled by events

Status

Approved

Context

The team has determined that the current design of the notifications modules, which relies on coupling via interfaces, is not flexible enough to allow for easy integration with other modules.

Decision

The decision is to decouple the notifications modules by using events as the only method of integration, where other modules can publish integration events can be consumed by the notifications modules event handlers.

Consequences

๐Ÿ‘ Pros:

  • Increased flexibility in the overall system design
  • Easier integration with other modules
  • Improved maintainability of the codebase
  • Reduced dependencies between modules
  • Cons of decoupling notifications modules by using events:

๐Ÿ‘Ž Cons:

  • Additional complexity in the codebase
  • Additional overhead of managing events and event listeners
  • The need to re-architect the current notifications modules
  • More difficult to debug and troubleshoot if there are issues with the event-based communication.

๐Ÿ‘ฎ Fitness Function

private readonly IEnumerable<Assembly> _solution = AssemblyLoader.GetSolution();
[Test]
public void GIVEN_solution_THEN_others_modules_should_not_have_dependency_on_any_notifications_module_type()
{
var notificationModuleTypes = Types.InAssemblies(_solution)
.That()
.ResideInNamespaceStartingWith(Namespaces.Notifications)
.GetTypes()
.GetNamespaces();
var othersModuleTypes = Types.InAssemblies(_solution)
.That()
.DoNotResideInNamespaceStartingWith(Namespaces.Notifications)
.And()
.DoNotResideInNamespace(Namespaces.Bootstrapper);
var policy = othersModuleTypes!
.Should()
.NotHaveDependencyOnAny(notificationModuleTypes);
var result = policy!.GetResult();
result!.FailingTypeNames!.Should()!.BeNullOrEmpty();
result.IsSuccessful.Should()!.BeTrue();
}

๐Ÿ“š Description

Checks that any interfaces or class from notifications modules is used in other modules. This checks compliance with a predetermined ADR to avoid coupling via interfaces in the notification module. It is used as a means of ensuring that the code base complies with this ADR and that new developers do not introduce violations of this decision by their lack of knowledge.


๐Ÿ‘ฎ Fitness Function

private readonly IEnumerable<Assembly> _solution = AssemblyLoader.GetSolution();
[Test]
public void GIVEN_solution_THEN_notifications_modules_should_use_integration_events()
{
var integrationEvents = Types.InAssemblies(_solution)
.That()
.ImplementInterface(typeof(IIntegrationEvent))
.GetTypes();
var eventHandlers = Types.InAssemblies(_solution)
.That()
.ResideInNamespaceStartingWith(Namespaces.Notifications)
.And()
.HaveNameContain(TypesNames.EventHandler);
var policy = eventHandlers
.Should()
.HaveDependencyOnAny(integrationEvents.GetNamespaces());
var result = policy!.GetResult();
eventHandlers.GetTypes().Should().NotBeEmpty();
result!.FailingTypeNames!.Should()!.BeNullOrEmpty();
result.IsSuccessful.Should()!.BeTrue();
}
internal static class TypesNames
{
internal const string EventHandler = "EventHandler";
}
internal static class Namespaces
{
internal const string Bootstrapper = "Divstack.Company.Estimation.Tool.Bootstrapper";
internal const string Notifications = "Divstack.Company.Estimation.Tool.Emails";
}
internal static class PredicatesExtensions
{
public static PredicateList HaveNameContain(this Predicates list, string name) =>
list.HaveNameMatching(SearchPatterns.ContainsNamePattern(name));
}
internal static class TypesExtensions
{
internal static string[] GetNamespaces(this IEnumerable<Type> types)
{
return types.Select(type => type.Namespace)
.Distinct()
.ToArray();
}
}

๐Ÿ“š Description

This function verifies that the notification module has event handlers that only consume integration events. It is used to ensure that the notification module adheres to an architectural decision (ADR) that specified that the module should be integrated with other modules through events. The function is checking that this design decision is being followed.

๐Ÿ Summary

Architecture governance is a system for maintaining consistency and quality in architectural decisions throughout the development process of a software project.

It involves having control over architectural rules and guidelines, and ensuring they are adhered to. It also promotes collaboration and communication among team members, and helps to align the software architecture with business goals and evolving requirements.

A key component of architecture governance is the use of a fitness function, which acts as a "cop" to ensure that developers do not break established architectural rules and measures the alignment of the software architecture with business goals. One way to implement this is through automatic fitness functions, such as using NetArchTest, a library for writing unit tests that check dependencies between packages, classes, and methods in the codebase to ensure adherence to predefined rules and principles such as separation of concerns and the single responsibility principle.

๐Ÿ”— References

Software Architecture: The Hard Parts Book

Kamil Grzybek Modular Monolith has arch tests examples

Architecture Fitness Functions Talk

NetArchTest Project

๐Ÿค๐Ÿป Follow me

๐Ÿš€ If you're looking for ways to improve your software development skills and stay up to date with the latest trends and best practices, be sure to follow me on dev.to!๐Ÿ“š I regularly share valuable insights and tips on software development, as well as updates on my latest projects.

Speaking of projects, I'd like to invite you to check out my modular monolith project on GitHub๐Ÿ’ป, called Estimation Tool. It's a great example of how you can use modularization to build a more scalable and maintainable system. The project is open-source and well-documented, so you can learn by example and see how I applied the concepts of modularization in practice.

https://github.com/kamilbaczek/Estimation-Tool ๐Ÿ”—

So if you're looking to level up your development skills, be sure to follow me on dev.to and check out my modular monolith project on GitHub. Happy coding!๐ŸŽ‰

Image of Datadog

The Essential Toolkit for Front-end Developers

Take a user-centric approach to front-end monitoring that evolves alongside increasingly complex frameworks and single-page applications.

Get The Kit

Top comments (0)

Qodo Takeover

Introducing Qodo Gen 1.0: Transform Your Workflow with Agentic AI

Rather than just generating snippets, our agents understand your entire project context, can make decisions, use tools, and carry out tasks autonomously.

Read full post

๐Ÿ‘‹ Kindness is contagious

Please leave a โค๏ธ or a friendly comment on this post if you found it helpful!

Okay