I have multi-tenant system, where each tenant is client with its own view of the world, own entities, catalogs etc.
I want to partition all "command" messages so that all commands corresponding to same tenant/client would be processed sequentially, which gives me nice ordered sequence of all commands and corresponding events for each tenant and ability to ignore OCC, versions etc. within one tenant's "realm".
Again, I can have multiple instances of command handler for scaling / HA reasons, but at any time only one command per tenant should be processed.
If we ignore Actor model for a moment, what would be the best way to achieve this using NATS?
There's ability to set a MaxAckPending setting in NATS, so I can:
js.QueueSubscribe(
"*.commands", // assume first wildcard is for tenantId
"cmd-handler",
func(msg *nats.Msg) {
...
},
nats.MaxAckPending(1),
)
but wouldn't that give me only one message in-flight at a given time across the queue group and across all tenants?
Registering consumers individually like js.QueueSubscribe("tenant-12343.commands"...) would be awkward, since tenants can be created / removed dynamically.
Is there better way I can leverage JetStream perhaps?
First question to answer is, are those tenants an actual tenants, requiring full isolation?
If yes, you might be interested in leveraging NATS accounts, which gives you true-multitenancy out of the box. https://docs.nats.io/running-a-nats-service/configuration/securing_nats/accounts
Though keep in mind that it would make dynamically changing setup more complex.
if that's not the case, and you just want to ensure that commands for each tenant are handled separately, To achieve behaviour you're asking for, you need to create Consumer per tenant. Consumer is a server-side construct and state, and it's ok to create and discard then dynamically.
I would probably discover your options using new JS client API, that makes reasoning about it easier https://github.com/nats-io/nats.go/blob/main/jetstream/README.md#jetstream-simplified-client