Eventually you will need to bring your code into productive use – i.e. you need to make sure it is executed and accessible for users or systems that want to interact with it.
That will involve some form of deployment, maybe automatically or semi-automatically – nothing I really want to talk about here.
What I want to talk about is whether and for what reasons you may want to consider a system setup that involves deployment to more than one execution environments that interact, if at all, via a network boundary. In other words we are talking about distribution as in Distributed Systems or, more specifically, Micro-Services.
To cut things short: The point that I want to make is that you should not distribute except for non-functional reasons – which in many instances require much less distribution consideration than you may be led to believe.
Good Reasons for Distributed Architecture
In an ideal world there would be just one all-encompassing code execution environment and all code we conceive would just be put there to solve the problem it was designed for. We would not worry about reliability, security, availability, performance. But that is obviously not the case.
Throughput: If your code needs to perform a lot of work, a single piece of hardware may not be enough to achieve the required throughput. In that case, you want to scale out (horizontally). That is you want to make your code run on multiple machines to perform more work in parallel.
Security: Some aspects of your code may need access to sensible data while other parts do not need such access. In that case you may want to have corresponding control flows performed in isolated environments that meet higher security requirements for access and maintenance.
Availability: Your code may serve under different quality of service expectations. For example some customers may be paying a premium to have top performance while others would be willing to accept some lagging performance during peak usage. Likewise you may want that some very demanding workload does not impact performance of end user interfaces. In both cases, driven by external requirements such as type of users or type of workload, you will want to separate workloads to different environments.
Bad Reasons for Distributed Architecture
So that was clear, right? In order to identify some bad reason for distributed deployments let’s make a thought experiment. Imagine we have some logical module “Order Management”, which is all about managing orders for something. We do not really care – but we are short on imagination and this is one of the typical examples. So, there are some aspects that make up our order management:
Let’s – for simplicity and to make sure you think beyond implementation code – call this the Order Management Module. Looking at this somewhat cohesive picture, you might think that it would be a good idea to turn the Order Management Module into an Order Management System that is deployed on its own and that others integrate with via remote APIs – i.e. a Microservice.
Let’s run some what-if experiments on that:
Availability: If Order Management is down but our DB is up: We cannot check on orders – just because.
Extensibility: Our system gets more complex and some other services need to be informed on order status changes to trigger some follow up workflow. Can the order management invoke my code? Nope. So we need to messaging? Yes!
Likewise: Special types of orders need extended data validation in scenarios that reuse the order management. How do these extensions get to the Order Management? And how do we enforce compatibility, if the Order Management is de-coupled from its extenders?
Scaling: Some scenario involving orders needs to be scaled out. How much do we need to scale up the individual parts like our Order Management exactly?
Refactoring: Let’s not even go there.
In short: This form of distribution comes at a high cost in terms of additional complexity due to the introduction of remote boundaries and possibly even split project management. It is almost, as if the Order Management is being developed and provided by an independent organization just like any old 3rd party system you need to integrate with.
Oh wait… it is exactly like that! Is that what you wanted?
We confused modularization and distribution – and essentially for no good reason but that it looked obvious in naming.
The better solution however would be to include the Order Management as a module within the system so that its capabilities are available and can potentially be part of any execution of anything in the system.
That does of course not imply that every workload or control flow should run everywhere.
For example: Probably the most obvious reason for separation is to have front-end performance not getting impacted by background work and to being able to scale background work independently from front-end work.
So our deployment would be more like this:
You can tell from the naming that we are less concerned with function but kind of function.
Indeed the Order Management as a named set of capabilities is simply integrated with the overall capabilities of the system. Checking on an order would be is directly from the front-end. Administration of the Order Management is be done directly from the front-end.
Mass checking and email notification jobs are however performed on background nodes.
If the Order Management needs to look for extensions to call, based on a type of order: That would be done in-place as part of the control flow that requires so – as by definition – it would all be part of the system and so potentially available when needed.
That is: Any kind of control flow of the system can be performed in any execution environment. We make however sure, based on smart non-functional reasoning, that this does not happen, if it violates our non-functional requirements.
With this post, finally that little series referenced below comes to an end. It has been a busy year so far and I did not get around to writing a lot. I will try to post some smaller pieces next.
Hope you stick around!