Service Autonomy is one of the principles from the service-orientation design principles that maximizes a services control over the execution environment. Service Autonomy is the foundation for the other service-oriented design principles. Service Autonomy increases service consistency, reliability, simplifies scalability and distribution by removing the reliance on shared resources which a service has little to no control over.
Service Autonomy is similar to the Single Responsibility Principle from the SOLID principles. Services with individual responsibilities that do not overlap or share resources can make independent choices that do not affect other responsibilities in your system. The ability to reuse services is greater when services are normalized and responsibilities aren’t coupled or overlapping.
Sharing resources between services creates coupling which makes maintenance or upgrading individual pieces of a system and doing a no down time upgrade difficult and in some cases impossible. The classic example of this is the use of a shared database between services or applications. It’s important to understand that sharing a database server instance isn’t the real problem, the problem is sharing schema. Two services that share the same table(s) have increased coupling which makes upgrading and maintaining individual parts of the system problematic.
Figure 1. Integrating by using a shared database.
Organizations that struggle with coupling between pieces of their system often have the one all encompassing database schema that the entire system is coupled to. Complicated change scripts, schema changes in one area creating bugs or compilation errors in unrelated areas are signs of tight coupling.
Figure 2. All encompassing shared database schema.
The shared database integration pattern is one of the worst decisions in software design but this is only one example of sharing resources between services. The same principles apply to any resource that services may share but the shared database is the most common and serves as a good example.
We see these kinds of problems in organizations that don’t spend the time to learn distributed systems design, messaging and integration patterns and often solve every problem by apply the N-Tier application architecture using a single gigantic database schema shared across all services.
Competitive Advantage and Evolvability
Organizations who follow better practices and design principles benefit from the following.
- Ability to deploy newer versions, bug fixes and changes at a faster pace.
- Ability to upgrade and maintain individual services without affecting others.
- Ability to upgrade and maintain individual services without down time.
- Ability to scale individual pieces of their system as more capacity is needed in an individual area.
- Ability to add new features quicker and cheaper.
- Ability to adopt newer technologies quicker and easier.
- Ability to achieve high availability is simplified.
Organizations who struggle with tight coupling in their systems suffer from the following.
- Ability to add features is expensive and takes a long time because of how many components are affected by the change.
- Ability to adopt new technologies is expensive and takes a long time because of how many components are affected by the change.
- Having complex upgrade procedures.
- Service interruptions every time an upgrade is executed. If the solution is sold to customers, these customers are down during these periods.
- Difficulty to scale is increased.
- Difficulty to provide high availability is increased.
With the increased focus on the cloud and horizontal scaling, companies who understand how to architect these systems have a significant advantage. Practices like continuous delivery and evolving systems at a faster pace are increasing in popularity. With large systems the ability to evolve with no service interruption is a must. Facebook, Netflix and many others have been public with their practices around deploying often and these practices are now trickling down to smaller companies. Mission critical services have no choice but to get this right. None of these practices or designs are new, large systems have been doing this for a long time.
Normalizing services helps to increase autonomy. Similar to database normalization, service normalization reduces redundancy by identifing cohesive logic and data to create an individual service that is independent and autonomous.
Increasing autonomy makes it easier to guarantee consistency and reliability. Identifying logic and data that goes together is key to reduce the overlap. A service should own a piece of data and business logic and should be the only one responsible for the business processes behind them. Changing a products name or price has nothing to do with stock availability for that product and since this data and the business logic behind them aren’t related, cohesive partitions start to appear where you can split responsibilities.
A service should be
- The only one responsible for mutating (changing) its data.
- The only one responsible for business logic related to its data.
Services are allowed to have duplicates of data owned by other services as long as this data is idempotent and immutable. It is very important to remember that a service is the system of record for its data and is the only once responsible for changing it.
Figure 3. Resources not shared between services.
Figure 3 shows how the Order service and Stock service are no longer coupled to each other because they own their own database schema. The tables are allowed to share foreign keys but the relationship between the tables is not allowed to exist (no referential integrity) because this would make it difficult for each service to be completely independent. Tables inside a service boundary can have referential integrity but not foreign keys in tables across services. By not sharing resources between services, coupling is decreased while autonomy is increased and opportunities for service reuse is greater because a service can be used without having to sever ties with dependencies. A service becomes a cohesive top to bottom unit.
An autonomous system owns its own data, resources and has it’s own state.
- No shared data store (such as a database).
- No shared in-memory state.
Establishing boundaries around each service isolates them from the interactions between the others. This allows each service to evolve without affecting the other services. A service contract enforces a boundary and acts as a public API (application programming interface) between it and the other services using the service contract.
A service contract can have many forms. The SOAP specification uses the WSDL format to describe a services publically exposed methods and types. In a message oriented system the messages are the contract. In HTTP based REST services the uniform contract of HTTP verbs (HEAD, GET, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH) the resources, media types and hyper links (HATEOS, Hypermedia as the Engine of Application State) define the service contract.