SOA(Service Oriented Architecture) is all about contracts. Contracts can either be managed explicitly by doing Contract First, Customer-Driven Testing, having POs manage the contract as an important asset as much as a UI is. Contracts can also be unmanaged or managed implicitly just pushing problems to consumers and making the whole org move slowly. So what is a contract anyway? Why should we care?
The contract is all Data, Behavior, and expectation involved in the services capabilities. It’s easy to see just the endpoint’s input and output data and forget about the rest. The rest is smaller things like data formats, access protocols, serialization formats, and do not forget about expectations. For instance let’s say you are returning a list of customers, if the data is in ascending or descending order it might affect your consumer code or tests. Contracts cab be versioned and we should have specific tests on the Implementation provider side in order to make sure we don’t break the contract by accident.
“REST” is the default way to create service architectures nowadays. It’s very common to think about our public REST endpoints as our contracts. That’s fine and true. However, they are not the only contracts the service might have. Easily I can mention 3 other contracts that can either break YOUR code or your consumers such as Shared Databases, Event Sourcing / CQRS, and Shared Libs.
Hidden Contracts: Shared Databases
It’s an anti-pattern to share datastore with different services. It’s okay to have that as a temporary and transitive state. However if that remains as the final state your will have a bigger problem — often called Distributed Monolith(Missing proper Isolation). What we need to realize in this case is the fact that the TABLEs will be our contract and we will need to maintain the microservices A, B, and C in SYNC. This sync will mean sharing pojos, DAOS and all sorts of persistence objects otherwise are easier for one application to break another. If you have SYNCs like this you don’t have microservices, you don’t have a monolith, you have something far worst with is a distributed monolith.
So the bottom line here is the TABLES are your contract and you will need to manage them. Database objects can be far worst because versioning, testing, and maintain a database are often much harder than doing that with proper application code with languages like Java, Kotlin, Scala, Rust or Go. We have more hidden contracts like Event Sourcing / CQRS.
Hidden Contracts: Event Sourcing / CQRS
Microservices are a specific flavor of SOA(Martin Fowler said once). In SOA/Microservices we avoid Distributed Transactions at all costs. One simple solution is to use the Sagas Pattern which can be distributed or centralized or also know as orchestrated or choreographic.
Sagas Pattern is often used with a form of Event Sourcing and CQRS. So now your services don’t share databases however you have bigger problems and lots of complexity to keep consistency but besides that, there is another consideration you need to realize: You have another hidden contract. Your hidden contract is your Events. Events are often represented with Pojos and exchanged with Kafka or Kineses in form of JSON messages.
This is much better than the Database Contract in the previous example of our distributed monolith. Because you use better tolling to version, test, and maintain this event(which are also contracts).
Hidden Contracts: Shared Libs
There is also another form of hidden contract, much more evil and silent. Shared Libraries. Shared libraries are evil because often engineers do not apply proper design and proper Lean dependency management doing these libraries.
Considering the 3 forms of contract I present so far: REST Endpoint, Shared Database, and Event Sourcing, this 4th form is the worst form of hidden contract because with the previous 3 you are only breaking other consumers(which is wrong) however now you are the one who will break :D I mean your service code.
Shared libs are contracts. Meaning they need to be versioned, tested, and maintained. The biggest issue is the fact that you can break your code and if you need to change something on the lib, in the case you need to break the contract there you will need to migrate all the services so the immediate benefit tends to be diluted over the time and because of pure waste, slowness, and pain. Scale and time change everything. Libs can be a trap easily and sources of complexity. Internal shared libs required a different mentality. Otherwise, they might kill your microservices approach.
All contracts should be:
* Versioned: Sometimes you will want to maintain multiple versions at the same time.
* Tested: By the provider at least explicitly and maybe even by the consumers.
* Maintained: You need to have strategies to deal with change.
* Documented: Explain how it works and what happens under the hood.
* Accessible via some form for Dynamic Catalog. Could be either via Eureka or any other tool.
Contracts should be managed explicitly
The main reason is that Services tend to have dependencies. OH but my services don’t have dependencies, are you should you have ZERO hidden contracts? Even if you don’t have dependencies are you sure other services or layers don’t depend on you like UI, BFFs, Mobile, Edge, etc…
Sometimes it will make more sense to make bigger services and SOA is totally fine about that since granularity is not prescribed in SOA. Maintain contracts is not a free lunch. However, ignoring contracts is not a free lunch either since you will slow down your development cycles and if everything is changing all the time will be hard to have STABLE development. STABLE development requires STABLE contracts.
The cost of maintaining contracts does not need to be forever, often companies limit the number of versions to be maintained to 3 or 5 as one example. There are several different techniques to deal with versioning and changes in contracts like:
* Global URI Versioning
* Resource Versioning (URI or MINE Based)
* Method Based
In sense of Deprecations there are multiple ways to deal with them like:
* Sunset Header
* Deprecated Keyword on OpenAPI 3.0
* Create Migration Utilities
* Doing Automatic Transformation at Runtime to the latest version(I did this a lot)
IF you want to know more details you check out this interesting post here.
Double-Down in SOA means Double down in Contracts. Complexity needs to live somewhere and changes will happen and they need to be managed and governed somewhere IMHO explicit contracts is the best way to do it.
Originally published at http://diego-pacheco.blogspot.com on April 29, 2021.