Contract owner discovery in micro-service architecture

anand pathak
5 min readOct 16, 2021

In micro-service architecture, it is hard to identify services that are dependent on the data produced by a producer service when the message from the producer is forwarded in a chain of services.

To better understand this, let's take an example of an e-commerce application, where customers can place an order and perform a transaction. later they can see all the transaction history.

Also suppose there’s a requirement that customers want to see transactions categorized by order type, e.g how much money spend on Fashion items or Electronics items, etc.

Let's also make an assumption that there are two separate teams

  1. one that owns order management services.
  2. another owns transaction and payment services.

When a customer places an order, the Order management service will pass information to the transaction service along with order details. Transaction service performs transactions and passes the information to Payment History service to store all transactions details.

Once the customer accesses the payment history, it categories the transaction based on order type and returns it.

Now, suppose the Order type is an enum or id something like FASHION, ELECTRONICS. In this case, the Payment history needs to map the enum or ids with a translation copy ( FASHION= ‘Fashion items’, ELECTRONICS='Electronics items').

In an ideal situation, the Payment History service should receive the localized order type( Fashion items,Electronics items` ) from the Order management service and should not do any mapping. but’s for the sake of explaining the problem assume this is the state of the service. Also in real-world scenarios, it is very hard to protect against domain leakage because there are multiple factors like delivery time, ownership, system context that can cause leakage.

Till this point, the system works fine, there isn’t anything to worry about. But the problem starts when the Order management service needs to add a new type of Order, say Grocery.

The Order management service only knows the payment transaction is consuming the value and is not aware of any other service in the chain consuming the value. And For Payment History service the owner for order type is Payment Transaction services. It doesn’t know that it's coming from order management service.

Typically in an organization, this type of situation is dealt with by announcing the change and anyone using it reports back with it. Or sometimes people do have context about the system and they know the stakeholders they need to communicate about the change.

problem is, it requires a human understanding of the system, which is not reliable and because the system goes under continuous changes its not easy to always know about where things can break if the changes will be applied in the system.

This leads to identifying issues at a later stage of development or during testing and if you are having a bad time, maybe on production.

Lately, I was thinking about how can this be solved or how the understanding can be improved in an environment where the system goes under continuous changes.

Below are some of the approaches I could think of to solve this problem of discovering dependency across multiple services.

Approach 1: Centralised proto/schema library for the internal services

If there’s a centralized proto/schema repository, each service when they pass some key/value to another contract can map the key type from the producer schema.

e.g. in the example mentioned above, suppose there’s a proto for order and another for payment, and order type is passed by payment service to payment history service, it can refer to the order proto’s `order_type` key

// order.proto
message Order {
string id = 1;
google.protobuf.Timestamp last_updated = 2;
int32 customer_id = 3;
OrderType order_type = 3;
}
message OrderType {
enum Type {
UNKNOWN = 0;
FASHION = 1;
ELECTRONICS = 2;
}
}
// payment.Protomessage Payment {
string id = 1;
int32 amount = 2;
OrderType order_type = 3; // import order.proto
}

This way service which produces messages can trace the services that are using the same key/value.

The same can be applied for open API/swagger as well. you can define a schema and then another service can ref to the other schema.

Advantage

  • One of the key advantages is if there’s a single repo, it easier to find out the relationship between schemas. Any IDE or simple find command is enough to identify services that are forwarding the key/value.

disadvantage

  • If the services are built with different languages and technology, it won’t be easy to migrate to the centralized repository for all the contracts.
  • If it's a single repository, a lot of people will be contributing to it at the same time and more maintainers will be required for the contract repo.

Approach 2. Distributed tracing

If there’s a service that needs to forward any key/value it received from another service, it can add tags to the span about which service did it came from and what key/value is forwarded.

By looking at the complete trace, it can identify if any service in the chain forwarded key/value and which all services are consuming those.

Advantage

  • This does not require any centralized repo to identify.
  • Not just which service forwarded key/value from proto but also which service is consuming the message forwarded by the service can be identified.

Disadvantage

  • This takes an assumption of existing service already having distributed tracing.
  • In case there are a lot of keys forwarded by a service, there will be a lot of tags in span.

Approach 3. Static analyzer and comments to represent field forwards

It may not be easy to move all the message contracts or protos to a centralized repo. so another option could be to add comments in all the repo wherever the contract is defined in the service.

example:

OMS service/*
@contract_dependencies
service: OMS
*/
type OrderResponse struct {
OrderType string,
Amount string,
}
Payment transaction service
/*
@contract_dependencies
service: PTS
depends: OMS
key: OrderType
*/
type PaymentResponse struct {
Amount string
OrderType string
}

Here the analyzer could run across different repositories and it can look for contract dependencies and can generate a mapping graph. From the above example, it can generate a graph where PaymentResponse depends on the OrderType key from the OMS service.

One key advantage of this approach is that it does not require any centralized repo.

Thanks for reading!!

P.S: I am not an expert in this domain and have not done any research around it. This is purely based on the recent incident I saw in the current organization. If you know any better solution please suggest in the comment.

--

--