What type of data should be passed to domain events? - domain-driven-design

I've been struggling with this for a few days now, and I'm still not clear on the correct approach. I've seen many examples online, but each one does it differently. The options I see are:
Pass only primitive values
Pass the complete model
Pass new instances of value objects that refer to changes in the domain/model
Create a specific DTO/object for each event with the data.
This is what I am currently doing, but it doesn't convince me. The example is in PHP, but I think it's perfectly understandable.
MyModel.php
class MyModel {
//...
private MediaId $id;
private Thumbnails $thumbnails;
private File $file;
//...
public function delete(): void
{
$this->record(
new MediaDeleted(
$this->id->asString(),
[
'name' => $this->file->name(),
'thumbnails' => $this->thumbnails->toArray(),
]
)
);
}
}
MediaDeleted.php
final class MediaDeleted extends AbstractDomainEvent
{
public function name(): string
{
return $this->payload()['name'];
}
/**
* #return array<ThumbnailArray>
*/
public function thumbnails(): array
{
return $this->payload()['thumbnails'];
}
}
As you can see, I am passing the ID as a string, the filename as a string, and an array of the Thumbnail value object's properties to the MediaDeleted event.
How do you see it? What type of data is preferable to pass to domain events?
Updated
The answer of #pgorecki has convinced me, so I will put an example to confirm if this way is correct, in order not to change too much.
It would now look like this.
public function delete(): void
{
$this->record(
new MediaDeleted(
$this->id,
new MediaDeletedEventPayload($this->file->copy(), $this->thumbnail->copy())
)
);
}
I'll explain a bit:
The ID of the aggregate is still outside the DTO, because MediaDeleted extends an abstract class that needs the ID parameter, so now the only thing I'm changing is the $payload array for the MediaDeletedEventPayload DTO, to this DTO I'm passing a copy of the value objects related to the change in the domain, in this way I'm passing objects in a reliable way and not having strange behaviours if I pass the same instance.
What do you think about it?

A domain event is simply a data-holding structure or class (DTO), with all the information related to what just happened in the domain, and no logic. So I'd say Create a specific DTO/object for each event with the data. is the best choice. Why don't you start with the less is more approach? - think about the consumers of the event, and what data might they need.
Also, being able to serialize and deserialize the event objects is a good practice, since you could want to send them via a message broker (although this relates more to integration events than domain events).

Related

How to decorate the final class DocumentGenerator

I am having problems to decorate the final class "DocumentGenerator" (in vendor/shopware/core/Checkout/Document/Service/DocumentGenerator.php) and overwrite the "generate" function inside of it.
I tried to decorate it the usual way, but an error is thrown because the "DocumentController" class excepts the original class and not my decorated one?
Argument 2 passed to Shopware\Core\Checkout\Document\DocumentGeneratorController::__construct() must be an instance of Shopware\Core\Checkout\Document\Service\DocumentGenerator
Its also not possible to extend from the class in my decorated class, because the "DocumentGenerator" is a final class.
My goal is to execute additional code, after an order document is generated. Previously I successfully used to decorate the "DocumentService" Class, but its marked as deprecated and shouldnt be used anymore. Also the "DocumentGenerator" class is used for the new "bulkedit" function for documents as of Version 6.4.14.0
I'm grateful for every tip.
As #j_elfering already wrote it's by design that you should not extend that class and therefore also shouldn't decorate it.
To offer a potential alternative:
Depending on what you want to do after a document has been generated it might be enough to add a subscriber to listen to document.written, check if it was a new document created and then work with the data from the payload for fetching/persisting data depending on that.
public static function getSubscribedEvents()
{
return [
'document.written' => 'onDocumentWritten',
];
}
public function onDocumentWritten(EntityWrittenEvent $event): void
{
foreach ($event->getWriteResults() as $result) {
if ($result->getOperation() !== EntityWriteResult::OPERATION_INSERT) {
// skip if the it's not a new document created
continue;
}
$payload = $result->getPayload();
// do something with the payload
}
}
Probably not what you want to hear but: The service is final in purpose as it is not intended to be decorated.
So the simple answer is you can't. Depending on your use case there may be other ways that don't rely on decoration.

DDD : Business Logic which need infra layer access should be in application service layer, domain service or domain objects?

For an attribute which need to be validated, lets say for an entity we have country field as VO
This country field needs to be validated to be alpha-3 code as per some business logic required by domain expert.
NOTE:
*We need to persist this country data as it can have other values also and possible in future there can be addition, updating and deleting of the country persisted data.
This is just one example using country code which may rarely change, there can be other fields which needs to be validated from persistence like validating some quantity with wrt data in persistence and it won't be efficient to store them in memory or prefetching them all.
Another valid example can be user creation with unique and valid domain email check, which will need uniqueness check from persistence
*
Case 1.
Doing validation in application layer:
If we call repository countryRepo.getCountryByCountryAlpha3Code() in application layer and then if the value is correct and valid part of system we can then pass the createValidEntity() and if not then can throw the error directly in application layer use-case.
Issue:
This validation will be repeated in multiple use-case if same validation need to be checked in other use-cases if its application layer concern
Here the business logic is now a part of application service layer
Case 2
Validating the country code in its value object class or domain service in Domain Layer
Doing this will keep business logic inside domain layer and also won't violate DRY principle.
import { ValueObject } from '#shared/core/domain/ValueObject';
import { Result } from '#shared/core/Result';
import { Utils } from '#shared/utils/Utils';
interface CountryAlpha3CodeProps {
value: string;
}
export class CountryAlpha3Code extends ValueObject<CountryAlpha3CodeProps> {
// Case Insensitive String. Only printable ASCII allowed. (Non-printable characters like: Carriage returns, Tabs, Line breaks, etc are not allowed)
get value(): string {
return this.props.value;
}
private constructor(props: CountryAlpha3CodeProps) {
super(props);
}
public static create(value: string): Result<CountryAlpha3Code> {
return Result.ok<CountryAlpha3Code>(new CountryAlpha3Code({ value: value }));
}
}
Is it good to call the repository from inside domain layer (Service
or VO (not recommended) ) then dependency flow will change?
If we trigger event how to make it synchronous?
What are some better ways to solve this?
export default class UseCaseClass implements IUseCaseInterface {
constructor(private readonly _repo: IRepo, private readonly countryCodeRepo: ICountryCodeRepo) {}
async execute(request: dto): Promise<dtoResponse> {
const someOtherKeyorError = KeyEntity.create(request.someOtherDtoKey);
const countryOrError = CountryAlpha3Code.create(request.country);
const dtoResult = Result.combine([
someOtherKeyorError, countryOrError
]);
if (dtoResult.isFailure) {
return left(Result.fail<void>(dtoResult.error)) as dtoResponse;
}
try {
// -> Here we are just calling the repo
const isValidCountryCode = await this.countryCodeRepo.getCountryCodeByAlpha2Code(countryOrError.getValue()); // return boolean value
if (!isValidCountryCode) {
return left(new ValidCountryCodeError.CountryCodeNotValid(countryOrError.getValue())) as dtoResponse;
}
const dataOrError = MyEntity.create({...request,
key: someOtherKeyorError.city.getValue(),
country: countryOrError.getValue(),
});
const commandResult = await this._repo.save(dataOrError.getValue());
return right(Result.ok<any>(commandResult));
} catch (err: any) {
return left(new AppError.UnexpectedError(err)) as dtoResponse;
}
}
}
In above application layer,
this part of code :
const isValidCountryCode = await this.countryCodeRepo.getCountryCodeByAlpha2Code(countryOrError.getValue()); // return boolean value
if (!isValidCountryCode) {
return left(new ValidCountryCodeError.CountryCodeNotValid(countryOrError.getValue())) as dtoResponse;
}
it it right to call the countryCodeRepo and fetch result or this part should be moved to domain service and then check the validity of the countryCode VO?
UPDATE:
After exploring I found this article by Vladimir Khorikov which seems close to what I was looking, he is following
As per his thoughts some domain logic leakage is fine, but I feel it will still keep the value object validation in invalid state if some other use case call without knowing that persistence check is necessary for that particular VO/entity creation.
I am still confused for the right approach
In my opinion, the conversion from String to ValueObject does not belong to the Business Logic at all. The Business Logic has a public contract that is invoked from the outside (API layer or presentation layer maybe). The contract should already expect Value Objects, not raw strings. Therefore, whoever is calling the business logic has to figure out how to obtain those Value Objects.
Regarding the implementation of the Country Code value object, I would question if it is really necessary to load the country codes from the database. The list of country codes very rarely changes. The way I've solved this in the past is simply hardcoding the list of country codes inside the value object itself.
Sample code in pseudo-C#, but you should get the point:
public class CountryCode : ValueObject
{
// Static definitions to be used in code like:
// var myCountry = CountryCode.France;
public static readonly CountryCode France = new CountryCode("FRA");
public static readonly CountryCode China = new CountryCode("CHN");
[...]
public static AllCountries = new [] {
France, China, ...
}
public string ThreeLetterCode { get; }
private CountryCode(string threeLetterCountryCode)
{
ThreeLetterCode = threeLetterCountryCode;
}
public static CountryCode Parse(string code)
{
[...] handle nulls, empties, etc
var exists = AllCountries.FirstOrDefault(c=>c.ThreeLetterCode==code);
if(exists == null)
// throw error
return exists;
}
}
Following this approach, you can make a very useful and developer-friendly CountryCode value object. In my actual solution, I had both the 2 and 3-letter codes and display names in English only for logging purposes (for presentation purposes, the presentation layer can look up the translation based on the code).
If loading the country codes from the DB is valuable for your scenario, it's still very likely that the list changes very rarely, so you could for example load a static list in the value object itself at application start up and then refresh it periodically if the application runs for very long.

DDD Entity and EntityType reference

I'm learning DDD and here is a problem I faced. I have two Aggregates (simplified):
class NoteType : AggregateRoot {
int noteTypeId
string name
string fields[]
... code omitted ...
}
class Note : AggregateRoot {
int noteId
int noteTypeId
map<str, str> fieldValues
setFieldValue(fieldName, fieldValue) {
// I want to check that fieldName is present in Notes.fields
// and later fieldValues[field.name] = fieldValue
}
... code omitted ...
}
I've heard that aggregates should reference to each other by ID's only. It this case I can't access NoteType.fields. I found several ways to do so, but not sure which one is better:
Pass NoteType instance into the Note model via constructor (do not reference by ID)
Use repository in setFieldValue to load NoteType
Use service which will do the check (this may cause all the Note logic to be implemented in this service, since Note highly dependent on NoteType)
What do you suggest?
What do you suggest?
Pass the information that the aggregate needs to the aggregate when it needs it.
setFieldValue(fieldName, fieldValue, noteType) {
// Now you have the data that you need to verify the noteType.fields
}
Sometimes, if you can't tell from outside the aggregate what information you need, then you instead pass the capability to look up that information
setFieldValue(fieldName, fieldValue, notes) {
// Use the provided capability to get what you need
noteType = notes.get(this.noteTypeId)
// the do the useful work
this.setFieldValue(fieldName, fieldValue, noteType)
}
Of course, if the only thing you need is the fields, then you might prefer to work only with that property:
setFieldValue(fieldName, fieldValue, fields)
Design is what we do, when we want to get more of what we want than we'd get by just doing it. -- Ruth Malan
In Domain Driven Design, a common "what we want" is to have the "business logic", meaning our implementation of the policies of information change that are important to our business, separated from the "plumbing" that describes how to read and store that information.

The performance issue of validating entity using value object

I have the following value object code which validates CustCode by some expensive database operations.
public class CustCode : ValueObject<CustCode>
{
private CustCode(string code) { Value = code; }
public static Result<CustCode> Create(string code)
{
if (string.IsNullOrWhiteSpace(code))
return Result.Failure<CustCode>("Code should not be empty");
// validate if the value is still valid against the database. Expensive and slow
if (!ValidateDB(code)) // Or web api calls
return Result.Failure<CustCode>("Database validation failed.");
return Result.Success<CustCode>(new CustCode(code));
}
public string Value { get; }
// other methods omitted ...
}
public class MyEntity
{
CustCode CustCode { get; }
....
It works fine when there is only one or a few entity instances with the type. However, it becomes very slow for method like GetAll() which returns a lot of entities with the type.
public async IAsyncEnumerable<MyEntity> GetAll()
{
string line;
using var sr = File.OpenText(_config.FileName);
while ((line = await sr.ReadLineAsync()) != null)
{
yield return new MyEntity(CustCode.Create(line).Value); // CustCode.Create called many times
}
}
Since data in the file was already validated before saving so it's actually not necessary to be validated again. Should another Create function which doesn't validate the value to be created? What's the DDD idiomatically way to do this?
I generally attempt not to have the domain call out to retrieve any additional data. Everything the domain needs to do its job should be passed in.
Since value objects represent immutable state it stands to reason that once it has managed to be created the values are fine. To this end perhaps the initial database validation can be performed in the integration/application "layer" and then the CustCode is created using only the value(s) provided.
Just wanted to add an additional point to #Eben Roux answer:
In many cases the validation result from a database query is dependent on when you run the query.
For example when you want to check if a phone number exists or if some product is in stock. The answers to those querys can change any second, and though are not suited to allow or prevent the creation of a value object.
You may create a "valid" object, that is (unknowingly) becoming invalid in the very next second (or the other way around). So why bother running an expensive validation, if the validation result is not reliable.

Domain Modelling: Neither an Entity nor a Value Object

In DDD, the domain model consists of entities and value objects, but what do we do when we need something in the model which is neither of these?
For example, I have introduced the following ScheduledItems<T> implementation in order to encapsulate scheduling specifics:
public class ScheduledItems<T>
{
private SortedDictionary<DateTime, T> scheduledItems;
public ScheduledItems()
{
scheduledItems = new SortedDictionary<DateTime, T>();
}
public void ScheduleItem(DateTime scheduledDate, T item)
{
scheduledItems.Add(scheduledDate, item);
}
public void RemoveItem(T item)
{
scheduledItems
.Where(x => x.Value.Equals(item))
.Select(x => x.Key)
.ToList()
.ForEach(k => scheduledItems.Remove(k));
}
}
This class will be used by a couple of entities for scheduling purposes.
At this point, this is neither an Entity (it has no identity) nor a Value Object (it is not immutable).
One solution is to turn it into a Value Object by making it immutable ('adding' or 'removing' items would return a new instance of ScheduledItems).
But is this really necessary for something which is not really associated to the domain? This class could be just like any other .NET collection.
That class looks like a repository for ScheduledItems. So ScheduledItem is the Entity and ScheduledItems is the the Repository with Add(), Remove() methods.
I guess it depends on why the items are sorted.
If they need to be sorted because of certain business rules then this should be part of your domain.
If they need to be sorted to be properly shown in the UI, then this most likely is just a bit of view logic that should not be part of the domain.
If none of the above, I would consider this a collection-like helper class that could be in a part of the infrastructure layer that could be used across the other layers.

Resources