In my example app there are admins and modules. Each admin may have some permissions to access a module. Also please note that I am assuming "simplified" DDD/CQRS where read models are allowed to read BC entities (entities from which aggregates in BC are "made" of). Also I am not considering event sourcing here. I only consider domain events which may "cause" some integration events (which are guaranteed to be delivered to interested parties).
I defined two BCs:
- admin BC
- admin_aggregate (root aggregate)
- admin_module_permissions (entity)
- module BC
- module_aggregate (root aggregate)
Admin aggregate looks something like this (simplified):
enum ModulePermissions {
read,
write
}
class AdminModulePermissions {
Guid module_guid;
ModulePermissions permissions;
}
class Admin {
void grantAccessToModule({
Guid module_guid,
ModulePermissions permissions,
}) {
// ...
// AdminModulePermissionsGrantedDomainEvent(admin, module, permissions)
// or
// AdminModulePermissionsChangedDomainEvent(admin, module, permissions, oldPermissions)
}
void revokeAccessToModule({
Guid module_guid,
}) {
// ...
// AdminModulePermissionsRevokedDomainEvent(admin, module, oldPermissions)
}
}
Now I need module read model that lists all modules to which admin has access to. Ideally would be to "get" list of modules admin has access to on higher level (identity) and pass it as a param to module read model like listModulesByGuids(List<Guid> moduleGuids)
. In such case module read model would not have to know anything about any module permissions. However as there might be thousands of modules that single admin has access to, it would be very inefficient to pass thousands of module guids/permissions to module read model... I have some ideas how to handle it but I am not sure which (if any) will not "break" DDD principles:
- Idea #1:
Leave admin BC and permission grant/revoke use cases as is, and fire integration events AdminModulePermissions(Granted | Changed | Revoked)IntegrationEvent
. Then make module read model to listen for those events and keep admin module permissions in read model entity. This way module permissions will be "duplicated" in admin BC aggregate and module read model. Module read model could then provide listModulesForAdmin(Guid admin_guid)
method which would list modules (along with permissions) for particular admin.
- Idea #2:
Keep module permissions entirely in module BC instead of Admin BC. This way there is no problem with listing modules for admin. However permissions are stored in different place that admin BC which can be as good as it can be wrong :D - not sure here. It's nice to have all permissions centralised in one place (admin BC), but on the other hand if there were more parts of the system (in addition to modules) that required other permissions then it could become complicated to manage all those in Admin BC. Again on the other hand if permissions to different parts were dependant somehow then it would be smarter to keep them in one place (admin bc). Please note that integrations would be used only by module read model and not module BC. So authorization before module BC use cases would still involve admin BC (it's read model actually, to check if particular admin can write/modify particullar module.
- Idea #3
This is something that could keep permissions entirely in Admin BC and on the other hand allow filtering in module read model. So, if there were thousands of modules then it would not make sense to return them all at once from module read model - they have to be paginated instead. My idea is that pagination could be done in admin read model and then just small set of module guids would be passed to module read model listModulesByGuids(justTenGuidsOrSomething)
. However pagination (reading admin read model) would have to be done in higher level like in controller or api gw or something like this. It's doable but it feels like unnecessary complexity. But maybe it's the way to go? I think it's called "view composition" (combining separate read models on higher level (api gw?).
So I am leaning towards #1 and #2 ideas because they allow to keep read models self sufficient but on the other hand this requires to keeps thing either duplicated or design "not-ideal" BCs. The question is if it's better to keep admin permissions centralized in admin BC and just spread them to other parts of the system (to read models not BCs!) by using integration events (with eventual consistency as permissions won't be available in other parts immediately). Or to keep permissions in particular part of the system. Or maybe #3 is the way to go?
Edit: Just realised that #3 won't work if it's required to order modules by any module fields (in this case it's not possible to paginate modules that admin has access to in admin read model).