I'm trying to refactor a codebase that was passing domain models to the views, to use DTOs instead.
Overall everything looks cleaner now, except one point: I have some business logic that belongs to the domain but needs to be executed in the view as well.
Example: I have an Activity class, that has a method to check whether a given User can edit it:
class Activity {
public function isEditableBy(User $user): bool {
...
}
}
The domain service layer uses this method to ensure that the Activity can be edited, and throw an exception if not.
The view uses this method to display an Edit button if the Activity is editable by the user.
Question: How to avoid duplicating this logic in the DTO and the model?
I thing you should not put logic in the DTO at all due to its definition "Data Transfer Object". So, "data" not "logic". Instead you can calculate result of the isEditableBy() method in your application service layer, put this result into a DTO and use the DTO in the view layer.
Related
I need to apply business rules in a DTO, but this DTO has properties of N entities.
I wanted to know the correct way to validate this DTO.
Very common approach would be to wrap DTO into an Entity and implement business rules inside the Entity.
var state = new DTO();
var entity = new Entity(state);
//this will change the state in a consistent way according to business rules
entity.DoSomething1();
entity.DoSomething2();
//this is C#, so I can't get immutable version easily, but if your language allows that - you should return immutable state from entity. Or you can return a clone of the state
state = entity.State;
//store or do whatever you like with the state as long as you keep it immutable.
_repository.Save(state);
As DTO is just a data-transfer-object, it should never apply any business logics inside.
DTO has properties of N entities
The preferable way is to create an aggregate class for your case and apply business logics inside.
public class Entitity1
{
}
public class Entity2
{
}
public class EntityAggregate
{
public EntityAggregate(Entity1 entity1, Entity2 entity2) // constructor
{
this.entity1 = entity1;
this.entity2 = entity2;
}
public ExecuteYourBusinessCase()
{
... access entity1 and entity2 here and evaluate business logics
}
}
Also it's worth metioning that one of DDD ideas is to prevent creating of invalid objects. So your should guarantee that if domain entity has been created it is valid and DTO can always be created. Business logics remains a black box for the layer responsible for DTO creation
As already stated in other answers:
You shouldn't have business rules in your DTOs.
Although, when the topic is DDD, another very common approach to ensure that you'll always create valid domain objects is to use the Builder Pattern.
This pattern allows you to vary a product's representation. For example, a software may have a domain denominated Product, but - in real world representation - it may be a Service or a Material (I can sell a cellphone or a cellphone insurance), so two builders must be created (MaterialBuilder and ServiceBuilder, for ie) that builds the same domain object, the Product.
This pattern generally is used with a method chaining and leads to a fluent interface.
I'm developing an application with Domain Drive Design approach. in a special case I have to retrieve the list of value objects of an aggregate and present them. to do that I've created a read only repository like this:
public interface IBlogTagReadOnlyRepository : IReadOnlyRepository<BlogTag, string>
{
IEnumerable<BlogTag> GetAllBlogTagsQuery(string tagName);
}
BlogTag is a value object in Blog aggregate, now it works fine but when I think about this way of handling and the future of the project, my concerns grow! it's not a good idea to create a separate read only repository for every value object included in those cases, is it?
anybody knows a better solution?
You should not keep value objects in their own repository since only aggregate roots belong there. Instead you should review your domain model carefully.
If you need to keep track of value objects spanning multiple aggregates, then maybe they belong to another aggregate (e.g. a tag cloud) that could even serve as sort of a factory for the tags.
This doesn't mean you don't need a BlogTag value object in your Blog aggregate. A value object in one aggregate could be an entity in another or even an aggregate root by itself.
Maybe you should take a look at this question. It addresses a similar problem.
I think you just need a query service as this method serves the user interface, it's just for presentation (reporting), do something like..
public IEnumerable<BlogTagViewModel> GetDistinctListOfBlogTagsForPublishedPosts()
{
var tags = new List<BlogTagViewModel>();
// Go to database and run query
// transform to collection of BlogTagViewModel
return tags;
}
This code would be at the application layer level not the domain layer.
And notice the language I use in the method name, it makes it a bit more explicit and tells people using the query exactly what the method does (if this is your intent - I am guessing a little, but hopefully you get what I mean).
Cheers
Scott
I'm trying to get my head around DDDD in Greg Young's style.
There is much talk about how to implement DDDD with CQRS+EventSourcing and there are some sample implementations ... and altogether it can be quite confusing...
In Gregs view aggregates don't have getters or setters - just state-changing methods which emit an corresponding event.
Basicly a event decribes a state-transitions that happend in the past. It's data describes what changed.
Some say, that this data can be "enriched" by additional data.
Where can this additional data come from?
i.e. I have User and Usergroup - both aggregate roots (can exist independently, have identity). User has a method called AddToUsergroup.
public class User : AggregateRoot
{
// ...
public void AddToUsergroup(Usergroup usergroup)
{
// validate state
RaiseEvent(new UserJoinedUsergroup(this.Id, usergroup.Id));
}
// ...
}
public class Usergroup : AggregateRoot
{
private string _displayName;
// ...
public void ChangeDisplayName(string displayName)
{
// validate state
RaiseEvent(new DisplayNameChanged(this.Id, displayName));
}
public void Apply(DisplayNameChanged e)
{
this._displayName = e.DisplayName;
}
// ...
}
If I would like to "enrich" the event with the name of the Usergroup (for debug reasons or something like this), how would I do that?
getters are nonexistent
internal state of usergroup is inaccessible
Injecting such things as repositories into User is not allowed (am I right here?!?), like
Read-side repository
Event-store repository
Bottomline questions:
Can Should something like repositories be injected to aggregate roots?
Should a event only use data available through parameters and internal state of the aggregate?
Should events only contain the minimum data describing the state-change?
And (little off-topic but the sample is here)
Should AddToUsergroup take an Guid instead of an full aggregate root?
Looking forward to your answers!
Lg
warappa
Should something like repositories be injected to aggregate roots?
No and there is no need to in this case. It can be appropriate to pass a domain service to a behavioral method on an aggregate, but again, not needed in this case.
Should a event only use data available through parameters and internal
state of the aggregate?
Yes, the original domain event should be such that it can be easily built by the aggregate and can be replayed in a deterministic fashion.
Should events only contain the minimum data describing the
state-change?
Yes. However, to address requirements of external subscribers, this is where content enricher comes into play. To dispatch a domain event externally, it is first committed to the event store then you can dispatch in the same tx or have an out of process mechanism which publishes events externally. At the point of publishing externally, you will normally use a different contract for the message since subscribers may need more than what is on the domain event itself. In this case, you need the user group name. The publisher, then, can pull up the user group name and place that data into the enriched event. This publisher can have access to a read-model repository for user groups which would allow it to retrieve the name.
Should AddToUsergroup take an Guid instead of an full aggregate root?
Yes. Passing the entire aggregate may not be possible and also creates the illusion that something other than the ID may be used, which should not be the case.
I'm using EF as my dataprovider to save and validate my items.
I have some custom validation logic which needs access to other services or items.
I know the ValidationContext class supplies these options.
My question is how can I get my own validation context with an service provider or item to EF?
When I implement the IValidateObject interface on my POCO entity I get an instance of the validation context, but where can I make sure my own ValidationContext is used instead of the EF default one?
I want to base some validation on the fact that a service or item is available in the ValidationContext
To pass the ValidationContext you need to override DbContext.ValidateEntity(). The method takes two parameters - entity entry and items. You would pass your validation context in the items dictionary. Take a look at this stackoverflow question.
I'm trying to figure out how to accomplish the following:
User can have many Websites
What I need to do before adding a new website to a user, is to take the website URL and pass it to a method which will check whether the Website already exist in the database (another User has the same website associated), or whether to create a new record. <= The reason for this is whether to create a new thumbnail or use an existing.
The problem is that the repository should be per aggregate root, which means I Cant do what I've Explained above? - I could first get ALL users in the database and then foreach look with if statement that checks where the user has a website record with same URL, but that would result in an endless and slow process.
Whatever repository approach you're using, you should be able to specify criteria in some fashion. Therefore, search for a user associated with the website in question - if the search returns no users, the website is not in use.
For example, you might add a method with the following signature (or you'd pass a query object as described in this article):
User GetUser(string hasUrl);
That method should generate SQL more or less like this:
select u.userId
from User u
join Website w
on w.UserId = u.UserId
where w.Url = #url
This should be nearly as efficient as querying the Website table directly; there's no need to load all the users and website records into memory. Let your relational database do the heavy lifting and let your repository implementation (or object-relational mapper) handle the translation.
I think there is a fundamental problem with your model. Websites are part of a User aggregate group if I understand correctly. Which means a website instance does not have global scope, it is meaningful only in the context of belonging to a user.
But now when a user wants to add a new website, you first want to check to see if the "website exists in the database" before you create a new one. Which means websites in fact do have a global scope. Otherwise anytime a user requested a new website, you would create a new website for that specific user with that website being meaningful in the scope of that user. Here you have websites which are shared and therefore meaningful in the scope of many users and therefore not part of the user aggregate.
Fix your model and you will fix your query difficulties.
One strategy is to implement a service that can verify the constraint.
public interface IWebsiteUniquenessValidator
{
bool IsWebsiteUnique(string websiteUrl);
}
You will then have to implement it, how you do that will depend on factors I don't know, but I suggest not worrying about going through the domain. Make it simple, it's just a query (* - I'll add to this at the bottom).
public class WebsiteUniquenessValidator : IWebsiteUniquenessValidator
{
//.....
}
Then, "inject" it into the method where it is needed. I say "inject" because we will provide it to the domain object from outside the domain, but .. we will do so with a method parameter rather than a constructor parameter (in order to avoid requiring our entities to be instantiated by our IoC container).
public class User
{
public void AddWebsite(string websiteUrl, IWebsiteUniquenessValidator uniquenessValidator)
{
if (!uniquenessValidator.IsWebsiteUnique(websiteUrl) {
throw new ValidationException(...);
}
//....
}
}
Whatever the consumer of your User and its Repository is - if that's a Service class or a CommandHandler - can provide that uniqueness validator dependency. This consumer should already by wired up through IoC, since it will be consuming the UserRepository:
public class UserService
{
private readonly IUserRepository _repo;
private readonly IWebsiteUniquenessValidator _validator;
public UserService(IUserRepository repo, IWebsiteUniquenessValidator validator)
{
_repo = repo;
_validator = validator;
}
public Result AddWebsiteToUser(Guid userId, string websiteUrl)
{
try {
var user = _repo.Get(userId);
user.AddWebsite(websiteUrl, _validator);
}
catch (AggregateNotFoundException ex) {
//....
}
catch (ValidationException ex) {
//....
}
}
}
*I mentioned making the validation simple and avoiding the Domain.
We build Domains to encapsulate the often complex behavior that is involved with modifying data.
What experience shows, is that the requirements around changing data are very different from those around querying data.
This seems like a pain point you are experiencing because you are trying to force a read to go through a write system.
It is possible to separate the reading of data from the Domain, from the write side, in order to alleviate these pain points.
CQRS is the name given to this technique. I'll just say that a whole bunch of lightbulbs went click once I viewed DDD in the context of CQRS. I highly recommend trying to understand the concepts of CQRS.