System Architecture: Bounded Contexts
Microservices architectures are currently highly fashionable. The question of how small a microservice can be is asked regularly. Is micro even small enough? Can we build pico-services? At Pluralsight, we have chosen to go a different direction. We are focusing on team size.
In order to maximize throughput, how big should a team be? One pair of developers? Ten pairs of developers? For the time being, we have settled on two to three pairs of developers with a Product Manager, a UX Specialist and a DevOps Engineer. Given this team structure, we ask ourselves how big of a chunk of our system can that group truly own?
We expect a team to own the discovery, design, development, delivery, and production support of their part of the overall system. This means that we have to divide the system carefully. If the creation of a new feature requires coordination across multiple teams, there will be ongoing questions about who should get alerted when there is an issue at 3:00 am. We like to avoid those alerts, but more importantly, we want to answer the question of who to alert ahead of time.
At Pluralsight, a Bounded Context is a part of the overall system that can be owned entirely by one team. A Bounded Context has clear boundaries. As it is supported by a Product Team, it might be considered a separate product in it’s own right.
Technical Characteristics of Bounded Contexts
Exclusive access to its data store(s)
This means that no other team or service or Bounded Context may access the database(s) used by this Context. This encapsulation of data allows the team to evolve their schemas more easily as they don’t have to coordinate with other teams. This also allows the team to change data storage technology entirely. You can move from flat files to Postgres to Redis to Cassandra and back again as the needs of the customer change.
Independent Build Pipeline
Teams may use the shared build and deploy servers but they have an independent build for their Bounded Context. Bounded Contexts also provision exclusive (virtual) hardware at our hosting company. This allows them to validate and deploy their components of the system on their own cadence without interfering with or putting at risk other parts of the overall system.
Deployable Units
A Bounded Context consists of one or more components such as websites, HTTP APIs, Windows Services/Daemons, apps, etc. All of these components are built to support the overall purpose of the Bounded Context and the team is responsible for the interactions between them.
Unified Domain Model
The team can create a model of the domain within the Bounded Context that includes all the rules of this part of the system with no concern regarding the rest of the system. This allows for highly tuned and expressive models which can improve communication and productivity.
Communication Patterns
Bounded Contexts are part of a larger system. We do not prescribe the architecture nor communication patterns within the Bounded Context. Instead we choose to trust that the team of professionals which owns the Context will make good choices. We do have some simple rules for communication between the Bounded Contexts.
Favor Asynchronous Communication
We temporally decouple the contexts from each other whenever possible. This is accomplished by passing JSON-formatted messages representing Business Events through a publish/subscribe message broker. This allows each Bounded Context to produce these Events at whatever rate is appropriate without worrying about the availability of consumers. Queuing allows each subscribing system to level off the load of incoming messages and scale consumers to meet that load. Teams cache the Business Events that they need in their own data stores so that they can execute business logic quickly without depending on external (to their Bounded Context) resources.
System APIs
Some operations are a poor fit for an asynchronous approach. Specifically, these are operations that are highly algorithmic or rely on highly stateful data. Examples include calculation of search results (imagine caching the results for all possible searches), calculation of the next question in a dynamic assessment engine, allocation of a limited resource such as a limited number of beta tester seats. For these cases, Bounded Contexts expose HTTP+JSON APIs that can be consumed by other Bounded Contexts within the system.
Delivery, Autonomy, Responsibility, Innovation
By building our system as a collection of Bounded Contexts, we have set each team up to succeed at delivering a quality product to our customers. Most of our teams are fully responsible for a small number of Bounded Contexts. This ownership allows our engineers to embrace responsibility which, in turn, encourages our leadership to trust our engineers with greater autonomy. Greater autonomy leads to greater innovation within Bounded Contexts as we try to regularly delight our customers.