Home >

The Gay Monolith Pattern for Closeted Microservices

The Gay Monolith Pattern for Closeted Microservices

Having the microservices cake and eating it too.

Posted on August 1, 2018 by Ernesto Garbarino

Introduction

Every application proposal trying to win the heart of a Fortune 500 company today not only needs to claim to be Agile—oh, sorry, SAFe—but also based on a microservices architecture. After high-profile microservices disasters like Dell’s, we understand that a myopic microservices approach may not end up well. What if we could have all the benefits of a microservices architecture but none of the drawbacks? Is this even possible? Maybe: enter the Gay Monolith architectural pattern.

In the good design patterns style, let me start off with the problem statement first and the solution later.

Problem Statement

Microservices are isolated network components with their own independent deployment and runtime life cycle. The challenge is that it is impossible to tell in advance how many microservices are—or should be—required. Even though bounded contexts may help identify microservices, it is not true that all relevant bounded contexts will be known at the start of the project. It is neither certain that there will be a 1:1 mapping between bounded contexts and two pizza teams: a single developer may work on two or more different bounded contexts especially in the early stages. The problems that arise as a result of churning out microservices in an uncontrolled manner are multiple fold:

Context Summary

Solution

An example of a Gay Monolith

An example of a Gay Monolith

A gay monolith has the external appearance of a regular monolith (single deployment, execution under one stack address space, and so on) except that, in reality it hides a set of closeted microservices waiting to come out whenever the conditions are appropriate. A gay monolith follows three fundamental rules:

  1. The initial gay monolith has one build and deploy process.
  2. General bespoke utility or helper functions (for example, libraries to connect to a Foreign Exchange (FX) and Shipping services) must be implemented as a different project with their own build process. Third party libraries (e.g. Flask) fall under this definition by default.
  3. Bounded contexts (e.g. Catalogue, Order, and Fulfilment) have no interdependencies:
    • All entry points to the bounded context are grouped together at the top level.
    • The implementation of the bounded context lives in a discrete module or package.
    • The implementation of the bounded context has no references to the top-level module/package.
    • The implementation of a given bounded context has no references to other bounded contexts.
    • If using a SQL RDBMS, JOINS are restricted to the bounded context at hand.

When to Let Microservices Come Out of the Closet

Conditions that suggest that a microservice should come out

Conditions that suggest that a microservice should come out

Once that we have a gay monolith, a key question is what are the conditions that suggest that a given bounded context must come out and become an independent microservice. Broadly speaking, the conditions are as follows:

Creating an Independent Microservice Out of a Gay Monolith

Step 1/2

Step 1/2

The first step consists in duplicating the gay monolith repo, and then applying the following changes to the cloned gay monolith:

  1. Removing unrelated invocations to other bounded contexts from the top microservices entry point.
  2. Removing the packages/modules applicable to the unrelated bounded contexts.
  3. Removing unrelated library imports from the build process.
Step 2/2

Step 2/2

The second step is removing all references to the bounded context that now lives in a separate microservice from the gay monolith so that we reduce its complexity and avoid leaving technical debt behind. Finally, we have to change any consumers’ endpoints so that they point to the new microservice as opposed to the monolith. This may not be necessary as explained further on.

Challenges and Solutions

Identifying the Bounded Contexts in the First Place

The whole rationale for the Gay Monolith pattern is that it is very hard to pin down what are the clear-cut bounded contexts at the beginning of the project. Thus, by having a single code base mounted on an IDE at the very beginning, moving functionality around is not a problem. The idea is that the bounded contexts will emerge organically as opposed to being shoehorned into ill-conceived, premature microservices.

URI Pollution and EndPoint Changes

Let us imagine we have two bounded contexts: RetailCatalogue and CorporateCatalogue. If both of them have a /browse URI, then we may have a clash which may lead us to define verbose URIs in the Gay Monolith such as /browseRetailCatalogue and /browseCorporateCatalogue. If this is undesired, then a virtual hostname approach may be used.

Using virtual hosts implies that the microservices need to be context aware. In the case of HTTP-based ones, this means that they need to be aware of the hostname used for the invocation. For example:

match host with
   "retail.company.com" ->
       match uri with
           /browse -> RetailCatalogue.browse()
   "corporate.company.com" ->
       match uri with
           /browse -> CorporateCatalogue.browse()

The above pseudocode requires that aliases are defined for the same gay monolith. As a bonus, this approach allows stripping the microservices off the gay monolith in a transparent manner—as far as service consumers are concerned—since it only involves replacing the host aliases with actual DNS A records.

Wrap Up

The Gay Monolith pattern is just common sense and has probably been described in numerous ways by a plethora of practitioners. My aim here was not claim any form of original thinking but just—in a spirit agianst bigotry—to come out with a cool metaphor in regards to how we ought to think about starting our microservices journey.

Monoliths is what we all start with and there is nothing wrong with them. But just by making them a little gay, we can have a development model we understand without introducing unnecessary complexities until we need them.