Tuesday, 18 June 2019

Local Microservices: First-Class Procedures

This is the third article in a three part series on local microservices.  The first two articles looked at:
These articles identified the method coupling of Object Orientation creates a monolithic jigsaw of different shaped objects.  Microservices are breaking these into more manageable, smaller jigsaws that appear similar in shape.

This article continues the breaking down to consider local (pass by reference) microservices.

Part Three: Local Microservices via First-Class Procedures

The first two articles in this series identified:
  1. Object references are a nice graph of nodes (objects) and lines (fields)
  2. Object methods have a significant coupling problem creating a jigsaw of behaviour
  3. Microservices break the method couple to return behaviour to a graph of nodes (microservices) and lines (HTTP requests, / Queue messages)
There is an underlying pattern to representing this decoupled behaviour.   It is HTTP URL / Queue name and payload / message type.   This decoupled client calling pattern can be represented with the following general interface:

interface ClientCall<T> {
  void invokeService(T singleObject);
}

This client calling interface is then implemented by the appropriate HTTP request service(...) method or Queue onMessage(...) method.  These methods are usually found on the following objects:

public void SomeHttpServicerImpl {
  @Inject SomeRepository someRepository;
  @Inject AnotherRepository anotherRepository;
  @Inject ClientCall<SomeArgument> anotherMicroservice;
  // other dependencies

  public void service(SomeObject httpRequestEntity) {
    // service HTTP request with injected dependencies
  }
}
public void SomeQueueConsumerImpl {
  @Inject SomeRepository someRepository;
  @Inject AnotherRepository anotherRepository;
  @Inject ClientCall<SomeArgument> anotherMicroservice;
  // other dependencies

  public void onMessage(SomeQueueMessage message) {
    // service Queue message with injected dependencies
  }
}

Furthermore, what is not shown clearly is the threading model.   As the HTTP servicer or Queue consumer are in their own process, they are run with their own threads.

The result is the following pattern for implementing the microservice:
  • Single object provided by client
  • Remaining objects are dependency injected
  • Thread used is based on service/consumer implementation
  • Interaction with other microservices is via single parameter ClientCall
The issue with this pattern is that all calls to other microservices require the microservice to be executed by another thread.  As the mciroservice resides behind HTTP requests / Queues, there is process boundaries preventing the calling thread from executing the microservice.

The process boundary separation provides a bounded context, so that the microservices are isolated from each other.  However, this separation puts a lot of communication overheads and network error handling into microservice solutions.  Plus it disallows microservices from being executed by the same thread.

So can we have the microservice called and executed by the same thread, and still continue to provide the microservice advantages of bounded contexts?  (in other words, the smaller jigsaws)

Local Bounded Context

To see how local (same thread calling/executing) microservices can be achieved, we need to transform the above implementations slightly.

Rather than field/setter injection, let's look at using constructor injection.  We could turn the above implementation into the following:

public void SomeMicroserviceImpl {
  private final SomeRepository someRepository;
  private final AnotherRepository anotherRepository;
  private final ClientCall<SomeArgument> anotherMicroservice;

  @Inject
  public SomeMicroserviceImpl(
            SomeRepository someRepository,
            AnotherRepository anotherRepository,
            ClientCall<SomeArgument> anotherMicroservice) {
    this.someRepository = someRepository;
    this.anotherRepository = anotherRepository;
    this.anotherMicroservice = anotherMicroservice;
  }

  public void service(SomeObject httpRequestEntity) {
    // service HTTP request with injected dependencies
  }
}

However, that's a lot of code!

Rather, why not just inject the dependencies directly into the method:

  public static void service(
            SomeObject httpRequestEntity,
            SomeRepository someRepository,
            AnotherRepository anotherRepository,
            ClientCall<SomeArgument> anotherMicroservice) {
    // service HTTP request with injected dependencies
  }

The method has effectively become a procedure.   The object and all it's fields are no longer necessary.  The above procedure links the required objects together by being parameters.

This execution is now:
  1. ClientCall used to invoke a procedure
  2. Procedure pulls in appropriate dependencies
  3. Procedure then invokes other procedures via the ClientCall interface
The execution is no longer methods navigating the Object references, locking you into monolithic jigsaw.  It is now procedures invoking each other, pulling in only the required dependencies for the procedure.

As the procedure pulls in only its required objects, it provides a bounded context.  One procedure may pull in a particular set of objects, while another procedure may pull in a totally different set of objects. As the procedure joins the objects, we no longer have to create a big graph of all objects referencing each other.   We can separate the objects into smaller graphs.  This break down allows separation of objects into bounded contexts.

Now the question comes of how can we implement this so the procedures run within the same process space?

First-Class Procedure

Well this procedure is remarkably similar to the First-Class Procedure.  See:
What the First-Class Procedures allows is containerising small snippets of logic within a procedure.  These procedures communicate via loosely coupled continuations that require only a single object (payload message).  The remaining objects are dependency injected.  Furthermore, threading models can be specific to each procedure.

The two bounded context approaches have similar characteristics:
  • HTTP/Queue communication can be considered a single argument Continuation
  • Threading models can be different within each first-class procedure / microservice process
  • Dependency Injection of both allows access to only the required object graph allowing smaller object jigsaw puzzles (no monoliths).  In other words, bounded contexts.
The difference is that the same thread can call and execute the First-Class Procedure.  In other words, First-Class Procedures run locally with each other.

Remote vs Local

But don't microservices want to be process separated to allow different release cycles and scalability?
Yes, that is absolutely true once up and running in production with heavy load of users.  However, what about getting started with microservices?

For me this falls into the problem of being opinionated too early.  To get the right mix of microservices takes a significant amount of requirements gathering and architecture.  Why? Because refactoring microservice architectures can be expensive.  Microservices involve a lot of overhead in typically different code repositories, build pipelines, network failure handling, etc.  Finding you got the microservice mix wrong involves a lot of effort to change.

By starting out with first-class procedures, you get to try a local microservice mix.  If the mixture is wrong, it is very quick to change them.  First-class procedures are weaved together graphically.  Therefore, to change the mixture is simply rewriting the procedures and then drawing the new connections between them. Yep, that's it.  No code moving between repositories.  No changing build pipelines.  No extra error handling because of network failures.  You can get on with trying out various mixes of local microservices (first-class procedures) all on your local development machine.

Once you find a mix you are happy with, deploy all of them in the one container.  Why?  Because unless you have a large user based, you can run your first-class procedures in just one node (possibly two for redundancy).  Having less deployed nodes, means less cloud instances.  Less cloud instances, is well less dollars.

Then as your load increases, you can split out the first-class procedures into separate nodes.  Just change the continuation link between them to either a HTTP call or Queue.  Furthermore, this split can then be for various reasons you may discover along the way:
  • differing functionality change cycles
  • differing team responsibilities (e.g. Conway's Law)
  • data governance may mean geographic restrictions
  • security may require some to run on premise
  • on premise capacity limits may mean pushing some to public clouds
The above is not an exhaustive list.

Having to requirements gather and architect the microservice mix given all of the above could get quite exhausting.  Especially, as some aspects are quite fluid (e.g. teams change, companies buying other companies, capacity limits on in house data centres, etc).  There are significant factors making it difficult to find the right mix of microservices up front.

Plus this also works in reverse.  As things change and some aspects don't experience higher loads or significant functional changes, they can be combined back into single instances.  This reduces the number of cloud instances required, and again reduces dollars.

Summary

For me, local microservices (i.e. pass by reference mircoservices) is going to eventuate.  This is similar to session EJBs being introduced because the EJB 1.0 specification of only remote calls was just too heavy.  Yes, we have better infrastructure and networks than 20 years ago.  However, the financial overhead costs of only remote microservices may soon be considered heavy weight and expensive given that local (pass by reference) First-Class Procedures are available.

So if you are finding the "remote" microservice architect a heavy weight and expensive consideration, try out First-Class Procedures as a local microservice solution.   This gets you many of the benefits of microservices without the expense.  Then, as your load increases and your revenues increase, scale into a remote microservice architecture.  But this is only where you see real need, so you can keep your infrastructure costs down.

Thursday, 6 June 2019

Local Microservices: Breaking up the Jigsaw

This is part two in a three part series looking at local microservices running in the same process.  Part one identified the coupling problems in Object Orientation behaviour (found here).  This article will look at how microservices are helping reduce the coupling problem.

Part Two: Breaking up the Jigsaw

Part one identified that object behaviour coupling is similar to a jigsaw puzzle. This is a highly coupled jigsaw of varying shaped objects. These different shaped objects makes their re-use and refactoring difficult within monolithic applications.

So, you may be asking how are microservices helping this problem?

For me, I see the evolution of microservices to be an evolution to break down the rigid behavioural coupling imposed by object methods.

We had applications grow to become monoliths:


This became unmanageable to enhance or re-use, as everything was tightly coupled together in a rigid jigsaw.

Our first attempts was to try re-using parts of the jigsaw with Service-Orientated Architectures.   This looked like the following:



The service-oriented architecture was, in my opinion, a doomed to failure attempt to the expose method connectors outside the system for re-use.  Yay, we can now call into our monoliths to re-use aspects of them.  Oh, wait there was too much coupling to that method that it was just too hard to separate it from the rest of the monolith.

Ok, we could put in governance and some great coding practices to avoid this.  However:
  • deadlines
  • shortcuts
  • new team members
  • occasional bad design decisions
  • etc 
allows the coupling to increase over time.  As much as we wanted to believe in the ideals, the behaviour coupling of methods just took over to create the monolith jigsaw.

We needed to break things down and keep them isolated.

So we split the jigsaw up into smaller puzzles.  The result is the following smaller puzzles joined by HTTP requests / Queues:


And for me, microservices were born.

At first glance, this looks very similar to the original EJB 1.0 specification of only remote calls.  Now one might argue that microservices are not single objects like EJBs typically were.  This, however, is not why I see microservices an improvement over remote EJBs.

EJBs use remote procedure calls that allow multiple parameters and varying exceptions to be thrown. This does nothing to decouple you from the varying shape of the method call. EJBs only enable methods to be called remotely.  These remote method calls continue to have all the jigsaw coupling issues of method calls.  Except, now they are less reliable due to network failures.  Hence, EJBs suffer the method coupling problems that leads you to a distributed monolithic jigsaw.

Microservices on the other hand, standardised calls to other microservices via HTTP requests / queue messages. Looking at the five coupling aspects of the method call (Inversion of Coupling Control), HTTP requests / queue messages remove most of the method call coupling:

Method Coupling AspectHTTP RequestQueue
Method NameURL

Though, can be decoupled indirection via lookup in service directory/registry
Outbound queue

Provides decoupled indirection to target consumer (via message routing)
Return TypeMay provide return entity.

However, typically this is only for front-ends with users waiting.

Note: be careful of synchronous returns between microservices, as they can create synchronous coupling that can lead to distributed monoliths
N/A, as decoupled from any synchronous return values
ParametersOnly single payload

Typically JSON/XML serialised object.
Only single payload

Typically JSON/XML serialised object.
ExceptionsStandardised REST status codes.

Note: be careful of relying on error response payloads, as they may be indications of cohesiveness problems in the microservice
N/A, as decoupled from any synchronous processing
ThreadingDecoupled as separate process

Any threading model enabling synchronous response
Decoupled as separate process

No restriction on threading model


Therefore, using HTTP requests / queues, the microservice calling shape is effectively standardised. Given the payload is typically a serialised object (e.g. JSON/XML), this removes the ability to pass multiple parameters.  Standardising the HTTP status codes disallows varying exceptions.  Plus, my preferred microservice communication of queues is decoupled by its very nature.

So by making the communication HTTP requests / queue messages, microservices are removing the method coupling problems.  It makes it easier to call different microservices, as the shape is only a name (HTTP URL / Queue name) and a single parameter (payload).

This, actually, is similar to the object reference shape of type and reference discussed in part one.  However, now type is parameter type (payload serialised object) and reference is name (URL / Queue name).  This allows microservice behaviour to somewhat be represented like the idealised object graph at the start of part one - lines to standard shaped microservice call connectors.

Stay tuned for part three where we look at further isolating the microservice container to the servicing method to effectively enable local microservices.