Assume I have an aggregate type of Order
, which contains OrderItems
. Depending on the state of an order, and the user-role operating on it, operations may be permitted or not permitted.
For example, if the user-role is Customer
and the order's status is Open
, then adding and removing items is permitted. Conversely, if the order's status is Processing
then adding and removing items is not permitted. However, if the user-role is Manager
and the order's status is Processing
then adding and removing items is permitted.
My question is:
Order
type handle these kinds of permissions? ie, it encapsulates all the logic about which roles can do what and therefore has a dependency on user-role.Order
type in a permissions service that would accept a role, an operation, and the subject of the operation (an order instance) and determine whether the action is allowed? ie, logic is externalized and assumed to be validated before the operation is executed on the Order
, which has no knowledge of the concept of user-roles.(Notes: The real-world use case is significantly more complex with large numbers of roles, statuses and actions. Authorization happens at an outer layer and has been applied already - this question is about instance specific permissions. In other words, a Customer is authorized to access the 'AddItemToOrder' API endpoint, but depending on the specific state of the order, the actual operation may or may not be allowed.)
Aggregates are a design pattern that play a big role in domain-driven development. In many systems, the relationships between entities can become so interwoven that attempting to eager-load an entity and all of its related entities from persistence results in attempting to download the entire database.
Tactical DDD is when you define your domain models with more precision. The tactical patterns are applied within a single bounded context. In a microservices architecture, we are particularly interested in the entity and aggregate patterns.
Domain-Driven Design(DDD) is a collection of principles and patterns that help developers craft elegant object systems. Properly applied it can lead to software abstractions called domain models. These models encapsulate complex business logic, closing the gap between business reality and code.
Repositories are classes or components that encapsulate the logic required to access data sources. They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer.
I prefer to treat access control as applicative logic rather than domain logic, because not all domain operation calls will come from human users and need access control. Putting Permissions in a separate, cross-cutting layer or in a different Bounded Context also helps with Separation of Concerns.
In your example, I would try to come up with distinct domain method names for actions taken by a Customer and a Manager. Enriching the ubiquitous language can be a good arbiter when you're struggling with similar but slightly different concepts.
I kind of feel that you are relying on technical details rather than on a ubiquitous language. The ubiquitous language would already tell you what to do. But I assume you don't quite have such a language, or at least you have not hammered the user permissions through it, yet.
Here is my take on it, assuming that you used the ubiquitous language when you asked your question. Let's see where it takes us.
Given current User has Customer role
And Order status is Open and it has 1 Item
When User adds an Item to Order
Then Order should have 2 items
Here is how I model such a language (as a unit test for expressiveness):
//given.
var user = new User(role: new CustomerRole());
var order = new Order(status: OrderStatus.Open, items: new [] { new OrderItem(name: "Item 1", price: 1.00m });
//when.
order.AddItem(byUser: user, item: new OrderItem(name: "Item 2", price: 2.00m));
//then.
Assert.Equal(2, order.Items.Count);
So far, as we can see, the answer is somewhere inside Order.AddItem
method. Most likely, inside it, you will have an expressive code like this:
public void AddItem(User user, orderItem item)
{
if (user.Role.CanAddOrderItem() && this.Status.IsEqualTo(OrderStatus.Open))
{
this.items.Add(item);
}
}
Now we can see that it's determined by both the Order
and Role
. Again, assuming that this is how your ubiquitous language sounds.
I hope this helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With