Everything you need to know about Contracts in Software Engineering

Everything you need to know about Contracts in Software Engineering

·

6 min read

Introduction

Hello everyone! hope everyone is having a productive year so far! In this article I'm going to be diving deep in contracts between services. By the end of the article we should have answers to the questions below:

  1. What are contracts?

  2. What are the types of contracts?

  3. Contracts tips & tricks

  4. Anti patterns with contracts

Each question will have its own segment so without further or do let's dive in.

What are Contracts?

Contracts are a written or spoken agreement, especially one concerning employment, sales or tenancy that is intended to be enforceable by law.

But what about in software engineering? Contracts are integration points in architecture and so many contract formats are part of the design process of software development; SOAP, REST, gRPC, XMLRPC are all examples of formats of contracts.

The definition can be re written to be:The format used by parts of an architecture to convey information or dependencies

Basically all the techniques used to "wire together" parts of a system.

Types of Contracts

There exists a spectrum of contract types but mainly three main types of contracts exist;

  • Strict Contracts

  • Less Strict Contracts

  • Loose Contracts

Strict Contracts

A strict contract requires adherence to names, types, ordering and all other details leaving no ambiguity. Many strict contract formats mimic the semantics of method calls. Remote Procedure Calls (RPC) is an example of a popular remote invocation framework that defaults to strict contracts.

Strict contracts are liked because they mimic semantics of method calls. However, they create brittleness in integration architecture. Something that is changing frequently and used by several distinct architecture parts creates problems in architecture.

Some examples of strict contracts include XML Schema, JSON Schema and RPC.

Some pros and cons of strict contract include:

Pros:

  • Guaranteed Contract Fidelity; Its always ensured that the contract adheres to the requirements.

  • Versioned; they generally require a versioning strategy to support two endpoints that accept different values or manage domain evolution over time.

  • Easier to verify at build time; Very important as it adds a level of type checking at build time

  • Better Documentation; the contracts provide the best documentation you'd need.

Cons:

  • Tight Coupling; If two services share the strict contract and it changes. The two services must change and adapt.

  • Versioned; mentioned here again because it could be a nightmare if the team doesn't have a clear deprecation strategy.

Less Strict Contracts

These exist in the middle of the spectrum. A perfect example for these are REST & GraphQL formats.

For REST, resources are modeled rather than a method or procedure endpoint making for less brittle contracts. For example if an architect builds a RESTful endpoint that describes parts of an airplane that supports queries about seats, that query won't break in the future if someone adds details about engines to the resource.

Similarly GraphQL is used by distributed architectures to provide read-only aggregated data rather than perform costly orchestration calls across a wide variety of services.

For example lets say we have the following Profile resource

Profile {
id
name
dob
address
country
}

If we have 2 services interested in accessing Profile information but each require different values for example Service A reads name and dob and Service B reads address and country, GraphQL is a great option for this case where you only request what you need from the resource. An anti pattern we'll talk about later called stamp coupling is a case where you request the whole resource when you only need 1 or 2 fields potentially decreasing response time across the network.

Keeping the contracts at a "need to know" level strikes balance between semantic coupling and necessary information without creating needless fragility in integration architecture.

Loose Contracts

At the far end of the spectrum lies extremely loose contracts. Often expressed as name-value pairs in JSON or YAML.

Just name-value pairs and nothing else.

Using loose contracts allows for extremely decoupled systems. However the looseness of the contracts comes with trade offs such as lack of contract certainty, verification and increased application logic.

Here's a pros and cons list of loose contracts.

Pros:

  • Highly decoupled; which is very useful especially in micro services architecture.

  • Easier to evolve; It evolves freely because no schema information exists.

Cons:

  • Contract management; harder to manage because problems such as misspelled names, missing name-value pairs can exist. These problems are fixable by having schemas.

  • Requires Fitness Functions. These are architectural functions run at build time to ensure that the required and necessary information exists in the contract.

Contracts tips & tricks

Let's for example take the following payload as a loose contract.

{
"name": "Martin",
"age": 24,
"city": "New York"
}

When this payload gets passed over the wire as JSON, it gets encoded (textual encoding) where it's processed as if it were a string as follows;

"{\"name\": \"Martin\", \"age\": 24, \"city\": \"New York\"}"

However there are other ways to potentially decreasing the size of the payload saving some bandwidth and increasing performance;

  • Using a binary encoding scheme; Binary encoding typically achieves smaller sizes compared to textual encoding by representing data more compactly and efficiently. For example, numeric values may be stored using fewer bytes, strings may be stored without delimiters, and repeated structures may be stored more efficiently. Binary encoding is generally very efficient in larger payloads. In small ones it might be costly more than normal textual encoding

  • Minification (shorthand notation); which aims to reduce the size of JSON payloads by using shorter key names while still maintaining readability and meaning. This technique is often used in situations where minimizing data size is critical, such as in network communication, storage optimization, or when working with large datasets. However, it's essential to balance the reduction in size with readability and maintainability. Minified keys should still be meaningful and understandable to developers who work with the data.

      {
      "n":"Martin",
      "a": 24,
      "c": "New York"
      }
    

Anti patterns with contracts

One of the most well known anti patterns is called "Stamp Coupling".

Stamp Coupling refers to passing large data structures between services but each service interacts with only a small part of the data structure. In other words, passing more than you really need to. Over specifying unneeded details can often lead to too much bandwidth consumption. Other problems can happen especially for strict contracts, if you specify a field that you probably didn't need and the contract changed (a change related to the field) then the service would break for no reason.

It's important to be careful and pass only what you need and not pass things just in case.

References

  1. Software Architecture The Hard Parts Book (Chapter 13)

  2. Designing Data Intensive Applications (Chapter 3)

Did you find this article valuable?

Support Amr Elhewy by becoming a sponsor. Any amount is appreciated!