I've taken on the challenge of modeling a simple User Voice-like system. High-level description:
- It's a portal for some SaaS users;
- They come and leave feature requests, suggestions, etc.;
- They should be able to vote/unvote for any suggestions;
- They can leave as many comments as they like on suggestions;
- Comments may be removed by the owner, but may not be edited.
I've modeled the domain as follows, using a DDD approach. Please advise about mistakes, warnings, improvements, etc.
I've also applied the advices from these posts:
public abstract class Entity
{
public Guid Id { get; protected set; } = Guid.NewGuid();
}
public class User : Entity // Aggregate Root
{
public string Key => $"{Email}:{MarketplaceUrl}";
public string Name { get; }
public string Email { get; }
public string MarketplaceName { get; }
public Uri MarketplaceUrl { get; }
internal User(string name, string email, string marketplaceName, Uri marketplaceUrl)
{
Name = name;
Email = email;
MarketplaceName = marketplaceName;
MarketplaceUrl = marketplaceUrl;
}
public Suggestion MakeSuggestion(string text)
{
return new Suggestion(this, text);
}
}
public class Suggestion : Entity // Aggregate Root
{
public string Text { get; /* a suggestion cannot be altered */ }
public User ByUser { get; }
public DateTime SuggestedAt { get; }
public ICollection<Comment> Comments { get; } = new List<Comment>();
public ICollection<Vote> Votes { get; } = new List<Vote>();
internal Suggestion(User byUser, string text)
{
ByUser = byUser;
Text = text;
SuggestedAt = DateTime.UtcNow;
}
public Comment AddComment(User byUser, string text)
{
var comment = new Comment(byUser, text);
Comments.Add(comment);
return comment;
}
public void RemoveComment(Comment comment, User userRemovingComment)
{
Comments.Remove(comment);
}
public void Unvote(User byUser)
{
var vote = Votes.SingleOrDefault(v => v.ByUser == byUser));
if (vote != null)
Votes.Remove(vote);
}
public Vote Vote(User byUser)
{
if (Votes.Any(v => v.ByUser == byUser))
throw new CannotVoteTwiceOnSameSuggestionException();
var vote = new Vote(byUser);
Votes.Add(vote);
return vote;
}
}
public class Comment : Entity
{
public string Text { get; /* a comment cannot be changed */ }
public User ByUser { get; }
public DateTime CommentedAt { get; }
internal Comment(User byUser, string text)
{
ByUser = byUser;
Text = text;
CommentedAt = DateTime.UtcNow;
}
}
public class Vote : Entity
{
public User ByUser { get; }
public DateTime VotedAt { get; }
internal Vote(User byUser)
{
ByUser = byUser;
VotedAt = DateTime.UtcNow;
}
}
public interface IUserVoiceStore
{
Task AddUserAsync(User user);
Task AddSuggestionAsync(Suggestion suggestion);
Task<Suggestion> GetSuggestionAsync(Guid id);
Task<User> GetUserAsync(Guid id);
// For when comments and votes are added/removed to/from a suggestion.
Task UpdateSuggestionAsync(Suggestion suggestion);
}
public class UserVoiceService
{
private readonly IUserVoiceStore store;
public UserVoiceService(IUserVoiceStore store)
{
this.store = store;
}
public async Task<User> RegisterUserAsync(string name, string email, string marketplaceName, Uri marketplaceUrl)
{
var user = new User(name, email, marketplaceName, marketplaceUrl);
await store.AddUserAsync(user);
return user;
}
}
public class CannotVoteTwiceOnSameSuggestionException : Exception { }
public class CannotRemoveCommentFromAnotherUserExcetion : Exception { }
CannotRemoveComment...Exception
anywhere? \$\endgroup\$