I have a relatively new project that employs a microservice architecture. I feel pretty good about the size and granularity of the individual services, with the exception or our security service.
I have three main services, let's say foo-service
, bar-service
, and baz-service
. These services never need to communicate, but all three services regularly talk via HTTP requests to the security-service
. I want this to stop for a variety of reasons- the biggest is that each request to my individual services spawns a request to the security service, which can turn into several extra hops once you account for load balancing, etc. I've been reading "Software Architecture Patterns" by Mark Richards, and he recommends in these instances you should share databases and violate DRY: copy the required functionality into each service. Still, he uses this example with smaller "utility" classes, which may not really apply in this instance.
The security service isn't that big, so I could definitely copy it into each of the other services. That said, it's just big enough that I don't feel great copying and pasting it - 314 'relevant' lines of code according to coveralls (java so there's a lot more actual code ;-). I could easily turn it into a module that each service brings in- but then my services have a shared dependency and that has bit me in the past. Of course the security code will grow over time as we add authentication methods, but we aren't reinventing the wheel when it comes to auth so It's mostly integrating with other libraries and authentication services. That is, I don't imagine this particular code base getting huge.
So my question, should I copy and paste the code or build a module that every service brings in? Thanks!
I want this to stop for a variety of reasons- the biggest is that each request to my individual services spawns a request to the security service, which can turn into several extra hops once you account for load balancing, etc.
PROS of leaving as a separate service:
- Changes to security business logic only affect the security service and do not need a change to the client services.
PROS of moving security logic into client services:
- Speed/performance.
- One less service to manage might mean reduced operation costs.
Speed (performance) might trump here, depending on what the requirements are, but it will come with increased development costs.
If you do move the security logic into its own re-usable module that can be called from within the other services just do a good job at encapsulating it and following basic lose-coupling-tight-cohesion design. Also, since you might have to defend this decision for years to come, please have a good explanation so your future boss does not fire you when she asks why does it cost so much to update our security logic. Have benchmarks readily available, people lie, numbers don't. I once had a one page benchmark results for a new DB I begged for. I was asked multiple times from different people as to why I went with the new DB... I would just send them the one page and never heard any further questions from that person.
This video might make you feel better with regard sto bucking the trend: https://www.youtube.com/watch?v=StCrm572aEs
It shows how and why Netflix bucked the trend and did not go with REST architecture for their API's. Basically architecture is a customer of requirements and cost, its not the other way around.
EDIT: Another big PRO for leaving as a service is that you might have to create multiple modules for each language being supported. At my job our security services are used by client services across multiple languages.
If you embed the security logic into the other services, then you really can't call it a microservice architecture now can you? I also understand that having that extra and duplicitous server round trip for every other service can be a bit of a drag. Here are some viable alternatives for you to consider.
Put all four of these microservices behind a firewall. Expose a public facing service that uses the security service to validate the incoming request and then call the other services if the request is valid for the given credentials. The other services always trust the caller which is a service owned and operated within your trusted environment.
If this is a "fire-and-forget" use case and you feel uncomfortable about that public facing service having too much orchestration responsibilities, then consider this alternative. The public facing service sends the inbound request to an unauthorized queue in a message broker. The security service consumes from that queue and performs the authentication. If valid, then the security service queues the message up on an authorized queue. Any number of microservices after that consume from the authorized queue and perform their respective operation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With