Key Principle: Support Large Agile Projects Through Architecture
Learn some architectural approaches that support small teams on large projects.
For a system’s architecture to support completely partitioning the work, some architecture work must be done. Some older systems can evolve toward a loosely coupled architecture, but for new systems the implication is that architecture work must be done up front to partition the work for multiple small teams.
Some Agile teams will balk at the idea of doing “BDUF” (Big Design Up Front), saying that it “isn’t Agile.” But as Stephen Jay Gould implied, when you take an approach whose core emphases cluster around keeping projects small and try to make it work for projects that are large, something has to give. You can’t change nothing and expect projects to scale successfully.
If Conway’s Law is fully considered, the only factor that really needs to be modified is the emphasis on emergent design and the planning needed to support that. A focus on up-front architecture with a goal of allowing work to be completely partitioned will keep teams small, which means the rest of the Agile emphases can remain. The focus on emergent design can also still remain within the highly partitioned areas in which each small team is working.
It is not coincidence that the focus on small Agile teams has coincided with the emergence of microservices architecture. The goal of microservices architecture is to structure an application as loosely coupled services. Similarly, the goal of structuring a large Agile project is to structure the human organization as loosely coupled small teams.
The organization that succeeds in architecting a large system to support completely partitioned work will not perceive itself as having a large project. It will feel like it has a collection of small teams working independently, with the only thing they have in common being that they all happen to be contributing to a common code base.
A lack of architecture leads to what a colleague refers to as the “snowflake effect”—each development feature is a unique snowflake that is designed differently from every other snowflake. This imposes significant cost on development as team members must learn the details of each snowflake to be able to work effectively in each area of the code. The larger the project becomes, the more significant this issue becomes. If you have enough snowflakes, eventually you have an avalanche!
Specific architecture suggestions
An architecture tutorial is outside the scope of this course, but the following sections contain thumbnail descriptions of architectural approaches that support small teams on large projects. The discussion is somewhat technical, so feel free to skip ahead if you are not technically oriented.
The basics: loose coupling, modularity
Strive for a loosely coupled architecture (modular and layered if possible) with readable and low-complexity code.
The architecture doesn’t need to be perfectly factored microservices code. It just needs to provide enough flexibility to support the business’s needs.
The holy grail that is sometimes described is to break your system into, say, 50 microservices. They can be highly modular, running in their own hosted containers with their own databases. They can each have their own versioned and authenticated APIs. They can all be released to production and scaled independently, which approaches the goal of having 50 completely partitioned development teams.
This is all a fantastic vision, and sometimes it actually works! But if some of the processing paths in your system call into numerous other parts of your system, which in turn call into numerous other parts of your system (known as “high fan out”), you can end up with significant processing overhead in the software and significant communication overhead among the teams working on the different microservices. You would probably be better off both in the software and the team structure to aggregate the system into fewer services.
Good solutions depend on applying a combination of technical judgment about design and management judgment about the team’s organization.
Avoid monolithic databases
Avoiding one big database helps support partitioned teams. Loosely federated databases can support loose coupling and strong modularity within teams. But, depending on the relationships among parts of the system, it’s also possible to create complex interactions that lead to significant overhead, latency, and opportunity for errors. A combination of technical judgement and team-management judgment is required to know how much to decompose a system to support loosely coupled teams while maintaining a high-quality technical solution.
Use queues
Decoupling or time shifting through the use of queues can also support loosely coupled development teams. In abstract terms, this consists of putting tasks into a queue for another part of the system to process later. The “later” could be microseconds later. The key concept in this guideline is that the system is not merely executing most of its code in an immediate, rigid, request-response loop. Using queues supports a high level of decoupling between key parts of system functionality, which allows for decoupling in the architecture and the development team (another instance of Conway’s Law).
It can be useful to think about key “seams” in a system architecture. A seam represents a boundary for which, within the boundary, there is a lot of interaction but across the boundary there isn’t much interaction. For the sake of loose coupling, it can be useful to use queues for the coupling across the seams. As with the microservices example, it’s possible to take this too far—50 processes managing 50 task queues with dependencies can create a different set of coupling issues, which could end up being worse than the problem the queueing is trying to solve.
Use design by contract
Design by contract is a design approach in which special attention is paid to interfaces (Meyer, 1992). Each interface is considered to have “pre-conditions” and “post-conditions.” The pre-conditions are the promises that the user of a component makes to the component about the conditions that will be true before the component is used. The post-conditions are the promises that the component makes back to the rest of the system about the conditions that will be true by the time the component completes its work.
Bearing Conway’s Law in mind, you can use design by contract to eliminate the impact of technical dependencies on workflow.
The “contract” will govern the interface between parts of the software system and will implicitly set expectations for the interfaces among humans too.
Get hands-on with 1400+ tech skills courses.