sorry for the long question, but I love DDD but there is a problem I can't seem to resolve.
We have a problem when adding business rules to the aggregate root.
Our architecture is mostly like this when we make a query from the front-end :
For a simple example, we have a Book
we want
- to persist
- fetch it.
The book has
Name
which has to be 10 characters
and an Author
which needs 15 characters.
Here the pseudo code in TypeScript:
class BookAggregateRoot {
private _name: string;
private _author: string;
private constructor(name: string, author: string) {
this._name= name;
this._author= author;
}
static create(name: string, author: string): BookAggregateRoot {
if(name.length < 10) {
throw error; // not really throwing, its just to show we have an error
}
if(author.length < 15) {
throw error; // not really throwing, its just to show we have an error
}
// all data is valid, create the Book
return BookAggregateRoot(name, author);
}
}
1st Scenario : save the book
Client -> DTO -> controllers -> useCases(1. calls create from Book class) -> calls repositories(persist the data) -> database -> data is returned, repositories(2. calls create from Book class) -> useCases -> mapperToDTO-> controller -> client.
2nd Scenario : fetch the saved book
Fetching data is in the same way but only, dont need to persist, only fetch the data : Client -> DTO -> controllers -> useCases(builds the query) -> calls repositories(query) -> database -> repositories(3. calls create from Book class) -> useCases -> mapperToDTO-> controller -> client.
As long as the business rules don't change, it's fine as the data stored and the data fetched has the same business rules.
The problem lies in the creating of the AggregateRoot. Let's say now my Book
needs a publishedDate
, all data that previously doesn't satisfy the data fetched from the database and will fail at step 3.
The class now becomes :
class BookAggregateRoot {
private _name: string;
private _author: string;
private _publishedDate: Date;
private constructor(name: string, author: string, publishedDate: Date) {
this._name= name;
this._author= author;
this._publishedDate = publishedDate;
}
static create(name: string, author: string, publishedDate: Date): BookAggregateRoot {
if(name.length < 10) {
throw error; // not really throwing, its just to show we have an error
}
if(author.length < 15) {
throw error; // not really throwing, its just to show we have an error
}
if(publishedDate.invalid) {
throw error; // not really throwing, its just to show we have an error
}
// all data is valid, create the Book
return BookAggregateRoot(name, author, publishedDate);
}
}
the data will always fail at step 3. above as the create requires a date.
We could create a migration to fix all the data... but is that the right approach?
thanx in advance and let me know if there is anything to better the design. :)