Vlad's blog

In programming veritas

Task Manager: application architecture and domain model

with 5 comments

This is the second post of a series of posts on developing Web applications using ASP.Net MVC. The previous post is available here. The source code is available on CodePlex.

Application architecture

The system has the following layers:

  • Presentation layer
  • Domain layer
  • Data Access layer
  • ORM
  • Database layer

The last two layers (ORM and Database) are more likely to complete the picture, because they have already been implemented by the Entity Framework.


Domain analysis

Preliminary analysis has identified the following main entities of domain model:

  • Project
  • Task
  • User

Project

Attributes Operations Invariants
  • Name
  • Description
  • List of tasks
  • Project log
  • Date/time of creation
  • Date/time of last modification
  • Create a new project
  • Close project
  • Reopen project
  • Add new task
  • Delete task
  • You can not add / delete a task, if the project is closed
  • You can not complete the project, which was already closed
  • You can not reopen the project, which was not closed
  • You can not delete the project if there are some tasks in progress

 

Task

Attributes Operations Invariants
  • Summary
  • Description
  • Project
  • Task log
  • Date/time of creation
  • Date/time of last modification
  • Start date/time
  • Owner
  • Assignee
  • Priority
  • Comments
  • Create a new task
  • Start task
  • Complete task
  • Reopen task
  • Reassign task
  • Change owner
  • Add a comment
  • You can not create a task which does not belong to any project
  • You can add a comment only for active task (started)

 

User

 Attributes   Operations  Invariants
  • Имя
  • You can not delete user if there some tasks in progress assigned to this user

Also the following secondary entities were identified.

  • Comment
  • Project log entry
  • task log entry

Domain model

According to the ideas of DDD you should try to implement business rules and invariants in the domain objects rather than creating separate abstractions which are out of scope of domain model. Most of the business logic is implemented in the methods of Project and Task. In order to monitor project state and task status the following types were added.

public enum TaskState
{
    NotStarted,
    InProgress,
    Finished
}
public enum ProjectState
{
    InProgress,
    Finished
}

One way of coding invariants is using of Code Contracts. For example, to verify that the task can start only if it has a state NotStarted, use the following code.

public void Start()
{
    Contract.Requires(State == TaskState.NotStarted);
    Contract.Ensures(State == TaskState.InProgress);

    State = TaskState.InProgress;
    Started = Updated = DateTime.Now;
}

It is important to comply with the invariant when constructing object. In order to exclude the situation when the Task object is created without adding to the list of projects tasks the Task constructor implemented as follows.

public Task(Project project, string summary, string description, User owner, User assignee, int priority, IUserActionLogger logger)
{
    Contract.Requires(project != null);
    Contract.Requires(owner != null);
    Contract.Requires(assignee != null);
    Contract.Requires(logger != null);
    Contract.Requires(!string.IsNullOrEmpty(summary));

    Comments = new List<Comment>();
    Logs = new List<TaskLogEntry>();

    Project = project;
    Summary = summary;
    Description = description;
    Owner = owner.Name;
    Assignee = assignee.Name;
    Priority = priority;
    Updated = Created = DateTime.Now;
    State = TaskState.NotStarted;
    Logger = logger;

    project.AddTask(this);

    Logger.LogCreateTask(this);
}

I.e. we pass a reference to a project in the constructor and then task adds itself to list of tasks. The client of Task class can invoke Project.Add after constructing an instance of Task. In order to avoid this situation Project.Add method is declared as internal.
The class diagram is as follows.

Services

In real life, not all business rules and invariants can be realized in the methods of domain objects without breaking Single Responsibility Principle . For example, most of the operations of Project and Task must be logged. (the log is not about the debug log, but about the information on which the user can observe what happened to the task or project). If we add logging infrastructure in the Task and Project it will break the Single Responsibility Principle. At the same time, if we move a large part of the business logic to any external services, then we get anemic domain model , which has state, but has no behavior.
In this case, it makes more sense to make a service IUserActionLogger which will encapsulate the details related to logging.

public interface IUserActionLogger
{
    void LogReassignTask(Task task, string oldAssignee, string newAssignee);
    void LogChangeTaskOwner(Task task, string oldOwner, string newOwner);
    void LogStartTask(Task task);
    void LogDeleteTask(Project project, string taskSummary);
    void LogCreateTask(Task task);
    void LogCreateProject(Project project);
    void LogCloseProject(Project project);
    void LogReopenProject(Project project);
    void LogAddComment(Task task, Comment comment);
    void LogCompleteTask(Task task);
    void LogReopenTask(Task task);
}

Here we must make a slight digression. The fact that the term service is too often used in different contexts. With respect to DDD is close to me the definition given by Eric Evans. He defines service as operation which is provided in domain model as separate stateless interface.
In addition to the logging service, we need services to maintain data integrity when you delete a project or user. For example, when we delete or close the project we need to make sure the project does not contain any active tasks. If found active tasks, an exception is thrown. To solve this problem we use interface IProjectDeleteService.

public interface IProjectDeleteService
{
    void Delete(Project project);
}

When we delete user we need to make sure the user does not have any tasks in progress. To solve this task we define IUserDeleteService interface.

public interface IUserDeleteService
{
    void Delete(User user);
}
Advertisements

Written by vsukhachev

December 29, 2011 at 11:17 am

Posted in Development, Uncategorized

Tagged with ,

5 Responses

Subscribe to comments with RSS.

  1. Perfect introduction to DDD and very professional representation , i hob you write more articles like that .

    Ahmed Fouad

    May 22, 2012 at 5:01 am

  2. Greate article. Keep writing such kind of information on your blog.

    Im really impressed by your blog.
    Hi there, You have done a great job. I will certainly digg it and personally suggest to my friends.
    I am confident they’ll be benefited from this website.

    Zak

    June 14, 2013 at 7:27 pm

  3. Hello there, just became aware of your blog through Google,
    and found that it is truly informative. I am going to
    watch out for brussels. I will appreciate if you continue this in future.
    Lots of people will be benefited from your
    writing. Cheers!

  4. I am extremely impressed with your writing skills as well
    as with the layout on your blog. Is this a paid theme or did you modify it yourself?
    Anyway keep up the excellent quality writing, it
    is rare to see a nice blog like this one today.

    Logo Nerds Coupon Code

    June 28, 2013 at 6:25 pm

  5. Hello to every single one, it’s actually a pleasant for me to go to see this site, it consists of priceless Information.

    architektura barok

    July 10, 2013 at 9:10 am


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: