π§ Zen of Services
In case reading the βπΌ Services all the way down π’β was your first introduction to services, and even if you have no extensive experience with it yet, working with the concept of services as your architectural backbone might still be hard to grasp or do.
In a way you could see services as an abstraction similar to how functions, also called routines, are abstractions for reusable code blocks. You could also see it as lego blocks that help you build a tower. But all these are in the end poor analogies that only get you so far. This chapter hopefully can serve together with other chapters of this book, code examples and more to aid you in your journey of becoming a service master.
See also the FAQ in case you are stuck. The answer might be there. If you do not find it you can either search elsewhere or find it for the answer yourself, send us an email, open a GitHub issue or join us on Discord.
Please do contribute improvements to this chapter and book as a whole, where you do find clarifications, answers and improvements yourself.
Tips and Tricks
π₯ Avoid recursive service stacking
In rama there are service implementations for Either (allowing you to
provide alternative implementations for two or more services),
and also blanket implementations for types such as Option, which work
more or less like Either, either using your middleware service
or not.
These are nice and convenient but be careful with them as they blow up your stack type.
Letβs say you have a service stack such as A > B > C > D > E.
In case you want to make B optional, by wrapping it with Option,
you would get now the type: A > [ B > C > D > E] | [ () > C > D > E].
As you can see your type became twice as long. Depending on your work machine you only have to do this a couple of times and your rust analyzer, cargo check, cargo build or any other Rust tool will suddenly become very slow.. Be careful of it.
So what is the solution? Make it possible to allow your middlewares to function
as identity layers (()). E.g. a Limit layer could be configured to also limit nothing at all. This way your type remains the same while still
allowing your layer to be optional depending on something like a cli flag.
π Stacks of services, not a service of stacks.
In your attempt to make a (layer) service as useful as possible it can be attractive to put a lot of configurations in your layer. Perhaps even to allow generic components that can also be nested.
Avoid this if possible. It makes using your service a lot harder and you are essentially reinventing the service concept that rama is build around.
Instead keep your service stack as flat as possible by allowing these configurations to instead be chained as you do with all your other layers,
and getting the optional configurations to be set and get via the Extension
that you can set via service inputs.
When doing so also try to keep these configurations as generic as possible.
For example, a client can insert a ProxyRoute or ordered ProxyRoutes
extension. The low-level proxy connectors only need the singular route selected
for the current attempt, while a ProxyRoutesConnector can try an ordered
route plan around an arbitrary connector stack. This keeps route discovery
(such as static configuration, middleware or PAC) separate from the protocol
connectors that establish one selected route.
ProxyAddress itself is deliberately not an extension. Wrap it in
ProxyRoute::Proxy, or include it in ProxyRoutes, so direct and proxied
connections use one unambiguous extension model.
Speaking about Connectors, those are a nice example of a type of Service that you might not expect. Because in rama we practise the zen of services: