How do you structure fauna to work in a micro-service architecture?
Fauna encourages us to keep our queries close to our code, but if your code is separated by service, how do you update and manage the system as a whole? If you make changes in one service, will it overwrite the fauna resources created in another? When you test, how do you access functions created by other services?
After thinking about this a bit, the only thing I can think of is to treat faunadb as its own service that setups the entire database and all it’s resources. Then import that module into each service that uses it.
It get’s tricky with testing though, because any change to the fauna service will require testing all services that depend on it and if they are in separate repos this would be a pain. I guess a monorepo with all services together would be easier to test.
Hey @miql. This is a topic I love to talk more and experiment more with. And I will do so in due time. First, it’s important to get some insight in your motivations to adopt microservices. Could you let us know which goals you are going for:
Separation of teams
The answer to your question is hard to provide since that depends entirely how you set it up. Are you looking into some kind of infrastructure as code and when you say fauna resources, do you mean functions/collections/indexes/roles etc or data? From your second part:
I think I can assume that you are talking about functions/roles/indexes/ etc and not data. This also gives a strong indication that you want you are aiming to use one fauna database for all services but are trying to keep the management of separate services cleanly separated. I would personally advocate for that approach as well since the alternative (a database per service) brings in eventual consistency. Although many people that take the microservices road might have already accepted eventual consistency and absence of transactions (and start using Saga patterns etc). Unless you need to work like that due to other requirements/reasons it probably is needlessly complex given that the database does scale. How I imagine setting up microservices with one database yet with clean separation per microservice would be as follows:
Generic setup for your CI:
Imagine we have a users service and a shopping cart service.
I would start by making two keys for your continuous integration:
User service admin key: can only manipulate user resources
Shopping cart admin key: same but for shopping cart resources.
I haven’t tried it, but I’m quite certain you can write a role for these keys with a Lambda on the schema where you only provide write/read access for example to manipulate the user collection, user indexes etc.
Throw in some FQL composition in the mix, and you can probably easily define a specification of which collections/indexes belong to which microservice and a generic FQL function “CreateUpdateRoleForService(“users”)” to make it easier to define such a role. Writing that role is a complete blog post of course so I currently can’t go that deep :).
What did we achieve so far? services now can have Keys that can only manipulate their own resources and can’t overwrite each others… that is… as long as you didn’t give both services access to the same resource. You could easily write a script that verifies that if you want to be certain. That means we can now easily create a CI flow per service.
Communication between microservices
Then there is, of course, the matter of communicating between services which might be needed at a certain point. Personally I would heavily embrace User Defined Functions there and use those as the ‘API’ of your service. That means that the user service is able to completely change the structure of documents or logic of a “CreateUser()” function without impacting the other services as long as it returns the same format. Joins are still possible by passing references to the UDF of another service since you can call other UDFs within a transaction, the most complex part there is probably indexes/collections that sit in between two services, for example the association collection of a many to many.
Alternatively you could create a view either synchronous (by calling a ‘UpdateShoppingCartUserView’ after a ‘CreateUser’) or asynchronous by streaming data to another collection and provide shopping cart with its own view of users.
This is all in theory since I didn’t get to materializing these ideas in code yet so at this point they might sound like ramblings of a madman. Let me know whether it makes sense
I think at this point, you are probably losing all the advantages of microservices except for the scalability of your processing logic. If that kind of scaling is the only thing you need (e.g. you have a small team that won’t run in each others way), you might as well use a monorepo imho since it will make your life easier. The above explanation is only useful in case you have many teams and want clear separation in ownership of certain resources.
That brings us back to what the motivations are for adopting microservices.