-
Notifications
You must be signed in to change notification settings - Fork 8
Open
Description
Hey, I like how you implemented ideas from Domain-Driven Design and Clean Architecture, I'm in the process of doing the same thing, but before I implement my own version, I'm studying how other people implement it in typescript. After studying your repository I can add a little bit of criticism and suggestions to your implementation, I hope it will be helpful for you.
- Your entities don't encapsulate data. All fields are public, therefore business logic can easily leak from the entity into other places and the object doesn't really control it's validity. I prefer not to use setters and getters, make all fields private and put as much business logic into entities and value objects as possible. If you need to export your data to the database or to your unit tests, you can use
export
method for that purpose which returns raw object with encapsulated data, but this method won't be used in the business logic. - Outbox can be overloaded. You don't want waves of load for one aggregate to affect another aggregate's event sending, therefore I prefer to create separate outbox table for every aggregate. You can also use tools like
debezium
to send messages from the outbox table. It doesn't necessarily have to be a relay. But relay is also okay, there's nothing wrong with that. - Outbox naming suggestions. Message which will be sent to outbox table can usually be either
event
orcommand
. The tuple which is located in the outbox table is calledMessage
. You also miss a little bit of data in the outbox table, therefore my preferred outbox naming would be:
class <AggregateName>Message {
messageId: number; // usually autoincrement, because it'll be easier to understand the order of messages
messageType: MessageTypeEnum; // event or command
messageName: string; // name of the event or command "WarehouseCreated"(event), "AddOrder"(command)
aggregateId: string; // necessary for partitioning
aggregateName: string; // Can be often useful (for example for humans or for id prefixes)
contextName: string; // Can be often useful (for example for humans or for id prefixes)
correlationId: string; // usually uuid or ulid, useful for log tracing and sagas.
payload: Record<string, unknown>;
// ...rest
}
- In my opinion, the more complicated your domain is, the less CRUD-like it should be. Because CRUD doesn't tackle complexity very well. In complex domains code will be much cleaner with more meaningful business commands. For example UpdateProduct(price, quantity) should be replaced by terms from the domain like ReceiveProduct(which will increase quantity), ShipProduct(which will decrease quantity), AdjustInventory(which will change the quantity depending on inventorization). These meaningful command will result more meaningful events which are much more convenient to work with.
If I'll find something else I'll make sure to write about it.
zhuravlevma
Metadata
Metadata
Assignees
Labels
No labels