0

While building microservice-based architecture I came across a dilemma on how to approach company level permissions for given range (or ids) of resources, i.e. allowing certain users to access subset of data for the company that the person is working at.

I have number of services, Invoices, Company, Orders etc. Each service has an access to corresponding data sources, Company has users table as well as company.

Company can have multiple users. Just to give a less abstract example: Invoices table has customer_id and company_id, but customer data lies within Company service's database, so there are no direct links.

Company admin can specify that certain user is able to see only invoices for specific customers.

And currently the only access that was checked was whether user has role for accessing certain action e.g. View Invoices. If yes, then Invoice service would return invoices for given user's company id. This check was done in Gateway service.

But now if I want to make it more granular i.e. return invoices only for given company AND customer_id I can extend the services endpoints to accept additional parameters, which is list of available customers. Which is pretty bad as one company can have thousand of customers, and sending ever increasing payload for each action seems like an overkill. That would also require expanding ALL dozen of endpoints just to pass that filter logic down to the repositories.

Another idea is to leave services to return the data in unchanged way but add additional filter on Gateway level that would return only those that match available customers for given user.

Third idea is to create separate service that would be called by each service for the list of ids that given user can access. But that feels like too much coupling as each service would have dependency on that specific one.

Do you have any additional ideas or good practices or even well described case studies on how to solve similar problem? Would you choose any of aforementioned solutions?

1 Answer 1

1

The underlying issue here is that your business requirement describes a high level of coupling between your services yet for an unspecified reason you are trying to force the process to be de-coupled.

  • This is sometimes an indication that your implementation is too granular. Can each of Invoices, Company and Orders services exist and operate independently of each other? If they cannot then should they really be separate micro-services at all. What is the benefit to the overall system to isolating these units of work from each other that couldn't have been achieved with a simpler SOA pattern?

Similar to implementing Tenancy, if the gateway has enough information about the caller and the target data that it can use filtering to implement to required business rules then that is probably the simplest solution. This would require all interactions between services to go through the gateway, which in turn increases the coupling of the overall system, but it becomes a necessary trade off.

Either all services and databases have a copy of the mapping rules, and there is a process to keep that data in sync, or you implement it centrally.

If the list of data is less about security and more about creating an arbitrary list of data that can be provided to a relatively small number of endpoints, then a solution that involves a list service that can return the list of records or Ids to the caller when required might be feasible. I use this for creating tag lists of records. In some of the application interfaces the user can select a number of records and save that as a Tag List, then the Identifier to that Tag List can be passed to service endpoints instead of trying to pass through an array of Ids that can be operated on.

This simplifies the contract interface to supporting arbitrary collections but also allows the list of data to be passed between multiple boundaries to an endpoint that needs it, without any of the intermediaries having or needing access to the actual data.

  • In a long chain of operations that manage the request but pass it on to the next operation in the chain, passing a list identifier can significantly reduce bandwidth consumption.

Another feature of a list service is that the list does not need to be static at all, it could be a dynamic list based on other business rules, or based on formula entered by an end user. An example might be a list of "Active customers in the 2019-2020 financial year" or a list of "Customers in Default or Financial Hold". Passing a list identifier decouples the list logic or reasoning from the actual services that need to process the data.

If the mapping of items to users is relatively static, then a List Service is probably overkill and gateway filtering might be easier to implement. Either way we do need to recognise and accept that you are introducing a degree of coupling or dependence between these services, and that is a necessary evil to achieve the desired business requirements.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.