开发者

Domain Driven Design: Repository per aggregate root?

开发者 https://www.devze.com 2023-02-10 07:16 出处:网络
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

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消