🧱 Modular
Modular, consisting of separate parts or units that can be joined together.
— Some random dictionary.
We advertise rama as a modular service framework to move and transform network packets. It's in fact our tagline. Next to that you can read in Why Rama that one reason for rama to exist is to allow you to easily create proxies without having to do everything yourself but while still being able to write your own code where you wish.
In that spirit we want to emphasise shortly in this chapter that it's important, as a rama user, to embrace this freedom. In one hand we want our modular components that we provide to be easy to use, and flexible where possible. However it would be a mistake to try to make each building block overly flexible to allow for any possible use case of it. First of all it would anyway be an impossible never ending task, and secondly it would seriously impact maintainability and the Developer Experience (DX).
We hinted before that because of rama you can build proxies without having to write everything yourself, but that when you want you can also write your own code. This last part does not only mean to make your own services and middleware from scratch, but can also mean that you copy on of ours and modify it to your needs. Doing so will give for a much cleaner solution then we can ever offer in a generic manner.
Modular Example
Let's dive into a real example where we struggled ourselves in finding this balance between giving enough flexibility and at the same time keeping it simple enough.
rama::service::layer::limit
is a generic middleware that allows you to limit what
requests can go through and which not. What this means depends a lot on
the Policy
used.
You could go as far as adding firewal capabilities to it, even
though we also have the
HijackService
for that.
From a consumer of a service wrapped in the limit
layer there are only two
outcomes:
- the request was able to proceed and you get the
Result
from the inner service; - the request did not go through and you got an error instead from the
Policy
used;
The latter case is also not immediately obvious and requires you to downcast
the returned error. Simple enough to do however and on you go.
However the story isn't that binary. There is information hidden her that could be useful. Questions you could still reasonably ask are:
- Was the request stuck in the queue (e.g. did it have to retry?)
- How many retries, or put differently, how long was it in the queue?
Depending on your proxy and how you use it, you might even want to expose this kind of information to your proxy users as to reduce their production speed once you start to queue, as a way to put backpressure and prevent from requests being eventually dropped.
For a long time we were wondering, or more aptly put, struggling. Should we also support these kind of use cases? But then what about the fact that this layer can be used both as a transport- and application middleware. And so on. Mental torture of the mind if you ask us.
And that brings us back to the topic of modularity, and the mindset one should
have when using rama
. Reuse when you can, fork and/or create otherwise.
As the maintainers of rama
we do in fact just the same with the dependencies
we may or may not rely upon. Sometimes it is just a lot easier to embed
a dependency in a more minimal form directly in your codebase then to try
to shoehorn a premade solution into your problem space.
With this we conclude the chapter. And now let's all repeat:
🖖 Reuse when you can, fork and/or create otherwise.