Jeff
Jeff

Reputation: 12183

DDD logic and Persistence Ignorance

Consider the following scenario:

public class Document 
{
  private ISet<User> sharedWith;

  public Document(string name) 
  {
    this.sharedWith = new HashSet<User>();
    this.Name = name;
  }

  public string Name { get; private set; }

  public IEnumerable<User> SharedWith 
  {
    get 
    {      
      return this.sharedWith;
    }
  }

  public void ShareWith(User user) 
  {
    if (this.SharedWith.Contains(user)) 
    {      
      throw new Exception("This document is already shared with that user.");
    }

    this.sharedWith.Add(user);
  }
}

Obviously this does not scale very well, because of the need to check the SharedWith for user existence, resulting in the ORM lazy-loading the entire collection into memory. I could do the existence check in the application service, but I consider this domain logic, and so it makes the most sense to me that I keep it in the Document class.

I can't seem to figure out how this should be done with DDD? And what if I am unable to use an ORM, how does one do this sort of stuff?

I suppose I should have a Document Aggregate and a User Aggregate?

I've looked at various DDD resources (I have not read the book though), but I can't seem to find an answer to this particular scenario.

Upvotes: 1

Views: 305

Answers (2)

Marco
Marco

Reputation: 2473

this was quickly done up so it's not perfect but you get the gist of it:

public class User { public Guid UserId { get; set; } }

public class Document
{
    public string Name { get; private set; }

    private ICollection<User> sharedWith = new List<User>();

    private DateTime? publishedOn;

    public Document(string name)
    {
        if (string.IsNullOrWhiteSpace(name))
        {
            throw new ArgumentException("Name is required");
        }

        this.Name = name;
    }

    public void Publish()
    {
        if (this.publishedOn.HasValue == false)
        {
            this.publishedOn = DateTime.UtcNow;
        }
    }

    public void SharedWith(User user)
    {
        if (this.publishedOn.HasValue == false)
        {
            throw new InvalidOperationException("Document must be published for sharing is allowed.");
        }

        sharedWith.Add(user);
    }
}

public interface IDocumentRepository
{
    Document documentOfId(Guid id);

    bool IsAlreadySharedWith(Guid documentId, Guid userId);
}

public interface IUseRepository
{
    User userOfId(Guid id);
}

public class ShareDocumentService
{
    private readonly IUseRepository userRepository;
    private readonly IDocumentRepository documentRepository;

    public void ShareWith(Guid userId, Guid documentId)
    {
        if (documentRepository.IsAlreadySharedWith(documentId, userId))
            throw new InvalidOperationException("Document has already been shared with user.");

        User user = userRepository.userOfId(userId);

        Document doc = documentRepository.documentOfId(documentId);

        doc.SharedWith(user);
    }
}

Upvotes: 2

Adrian Thompson Phillips
Adrian Thompson Phillips

Reputation: 7158

I suppose if you were modelling this in a paper/actor based world, then someone would have the job of marshalling who has access to which document(s) and this would probably rely on some sort of paper based artefact. To gain access to a document you would have to fill out a Document Request Form, which might go through an approval process.

This form in a paper based world, would become the many-to-many linking entity that becomes the key to user's accessing secure documents. It would make User, Document and DocumentRequestForm three separate entities.

Upvotes: 1

Related Questions