Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Solve apparent need for outside reference to entity inside aggregate (DDD)

I'm trying to follow DDD principles to create a model for determining whether or not an Identity has access to an Action belonging to a Resource.

A Resource (e.g. a webservice) is something that holds a number of Actions (e.g. methods), which can be accessed or not. An Identity is something that wants to access one or more Actions on a Resource. For example, someone uses an api-key in a call to a webservice method, and it must be determined whether or not access is allowed.

As I currently see it, Identity and Resource are aggregate roots, and Action is an entity belonging to Resource. It doesn't seem to make sense for an Action to live on its own; it will always belong to one Resource. An Identity needs to know to which Resource Actions it has access. This seems to suggest the following model.

Model

However, as I understand it, this violates the principle that something outside an aggregate cannot reference an entity within the aggregate. It must go through the root. Then I'm thinking, what if Action was the aggregate root and Resource an entity? But that doesn't seem very logical to me. I've also been thinking of merging Resource and Action into one entity, which would then be an aggregate root, but that also seems wrong to me.

So it leaves me kind of stuck on how to model this correctly using DDD principles. Anyone have a good idea on how to model this?

Update: The model I'm trying to create is the identity model for defining which resource actions an Identity is allowed to access. It is not a model for the actual implementation of resources and actions.

Update 2 - invariants: Id of all objects is given at birth, is unique, and doesn't change. ApiKey of Identity must be unique across all Identities. Name of Action must be unique within aggregate, but two different Resources can have Actions with same names, e.g. Resource "R1" can have an Action "A1" and Resource "R2" can also have an Action "A1", but the two "A1"s are not the same.

like image 702
maze_dk Avatar asked Jun 14 '26 13:06

maze_dk


1 Answers

Query or Write Operation?

The domain model in terms of aggregates and entities has it's purpose in DDD in order to simplify expression and enforcement of the invariants - as write operations are applied to the model.

As mentioned in @VoiceOfUnreason's answer, the question 'Can this user do action A on resource R' is a question that doesn't necessarily need to flow through the domain model - it can be answered with a query against either a pre-projected read-only model, or standard SQL querying against the tables that make up the write model persistence (depend on your needs).

Splitting Contexts to Simplify Invariants

However, your question, whilst mostly about how to identify if an identity is allowed to carry out an action, is implicitly seeking a simpler model for the updating of resources, actions and permissions. So to explore that idea... there are implicitly two types of write operations:

  1. Defining available resources and actions
  2. Defining which resource action combinations a particular identity is permitted to carry out

It's possible that the model for these two types of operations might by simplified if they were split into different bounded contexts.

In the first, you'd model as you have done, an Aggregate with Resource as the aggregate root and Action as a contained entity. This permits enforcing the invariant that the action name must be unique within a resource.

As changes are made in this context, you publish events e.g. ActionAddedToResource, ActionRemovedFromResource.

In the second context, you'd have three aggregates:

  • Identity
  • ResourceAction
    • Properties: Id, ResourceId, ResourceName, ActionId, ActionName
  • Permission

ResourceAction instances would be updated based events published from the first context - created on ActionAddedToResource, removed on ActionRemovedFromResource. If there is a resource with no actions, there is no ResourceAction at all.

Permission would contain two identity references - IdentityId and ResourceActionId

This way when carrying out the operation "Permit this user to do this action on this resource" the operation is just to create a new Permission instance - reducing the set of operations that affect the Identity aggregate's consistency boundary - assuming there are no invariants that require the concept of a 'permission' to be enforced within an Identity aggregate?

This also simplifies the query side of things, as you just need to search for a Permission entry with matching identityId, resourceName and actionName after joining Permissions to ResourceActions.

Responsibility Layers

The DDD Book in the section on Strategic Design refers to organising your contexts according to responsibility layers. To use the terms from the book, the above suggestion is based on the idea of a 'capability' responsibility layer (defining resources and actions) and an 'operational' responsibility layer (defining identity permissions and checking identity permissions).

like image 124
Chris Simon Avatar answered Jun 17 '26 23:06

Chris Simon



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!