We are starting on a greenfield project in ASP.NET MVC. I've used the MVC pattern in other stacks (PHP and Ruby) and I keep running into the same problems of where to place business logic, UI logic, and where should this system interact with other remote systems (i.e. web services and e-mail).
I was reading Advantages of ViewModel in MVC and the author states that there are 5 kinds of "logic" in an application:
Business Logic – Is a part of Model
Database Logic – This is the one of the non-spoken layer in MVC. Many people create a separate layer for this and invoke them from Business layer (model) or some keep them inside Model.
User Interaction logic – written inside controller
Presentation Logic – Presentation logic is the logic which will handle the presentation of a UI. Example – If some value is greater than 100 it should be displayed in green color. Basically it is a part of View.
Data transformation logic – In some situations we have some data with us and we want to display it in a different format. Example we have Date-of-birth and we want to display Age. Again this transformation becomes the part of View.
I would add one additional "kind of logic" which is:
- User Interface/Domain Model Manipulation Logic - How to transfer data from the view to data and method calls in the Domain Model
I believe the first 5 kinds of logic have defined areas already. Business Logic goes in the Domain Models. Database Logic goes in the Repositories or O/RM layer. User interaction logic goes in the controller. Presentation logic goes in the View Model. Data transformation logic goes in, essentially, classes that format data, such as dates and numbers.
The last kind of logic, where you must take data from the view and manipulate the Domain Model accordingly, doesn't seem to have its own place.
I've read about two possibilities:
A "service layer" in the application, although no mention is made of Web Services, but using "service objects" to encapsulate this
A "unit of work" object - This has been especially frustrating because a database transaction is an implementation of the Unit of Work Pattern. Recent variations of this pattern augment the database transaction with the actual manipulations of the Domain Model.
An Action/Executor object (Action/Executor Pattern)
Command objects (Command Pattern)
Creating a new blog post is easy - insert the post into the database. Creating a new user is not so straight forward, because you might want to send a welcome e-mail after inserting the user into the database, so additional operations are performed after the "unit of work" is complete.
A small code example.
Let's say we have a blog application.
- A blog has many posts
- A blog post cannot exist without a blog
- A blog post must have a title, body and publish date
This leads to two Domain Classes: Blog and BlogPost. To prevent you from accidentally creating a BlogPost object and adding it to the incorrect Blog, the constructor for the BlogPost class is marked internal
so you are forced to add a blog post through the Blog object to which it belongs:
public class Blog
{
public Blog(string name)
{
Name = name;
posts = new List<BlogPost>();
}
public int Id { get; internal set; }
public string Name { get; private set; }
public string Description { get; set; }
private IList<BlogPost> posts;
// Return a read-only collection of posts to the public
public IEnumerable<BlogPost> Posts
{
get { return posts; }
}
// Create a new BlogPost object to ensure it is attached to the proper Blog
public BlogPost AddBlogPost(string title, string body, DateTime publishDate)
{
BlogPost post = new BlogPost(this, title, body, publishDate);
posts.Add(post);
return post;
}
}
And the BlogPost class:
public class BlogPost
{
// This constructor is "internal" so code outside this Assembly must call
// Blog.AddBlogPost(...) to create new posts
internal BlogPost(Blog blog, string title, string body, DateTime publishDate)
{
Blog = blog;
SetTitleAndBody(title, body);
PublishDate = publishDate;
}
public int Id { get; internal set; }
public string Title { get; private set; }
public string Body { get; private set; }
public Blog Blog { get; private set; }
public DateTime PublishDate { get; private set; }
public int BlogId
{
get { return Blog.Id; }
}
public bool IsPublished
{
get { return PublishDate < DateTime.Now; }
}
public bool IsUnpublished
{
get { return !IsPublished; }
}
public void PublishAt(DateTime newPublishDate)
{
PublishDate = publishDate;
}
public void SetTitleAndBody(string title, string body)
{
if (string.IsNullOrEmpty(title))
throw new ArgumentNullException("title");
if (string.IsNullOrEmpty(body))
throw new ArgumentNullException("body");
Title = title;
Body = body;
}
}
The View Model for a Blog Post might look like:
public class BlogPostForm
{
public BlogPostForm()
{
}
public BlogPostForm(BlogPost post)
{
Id = post.Id;
Title = post.Title;
Body = post.Body;
PublishDate = post.PublishDate;
BlogId = post.BlogId;
}
[Required]
public int Id { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string Body { get; set; }
[Required]
public DateTime? PublishDate { get; set; }
[Required]
public int BlogId { get; set; }
}
Note that the PublishDate is a nullable DateTime because the user might submit the form without filling out the publish date. Furthermore all properties are public get/set, unlike the Domain Model, again because the user might submit the form without filling in those fields.
Now something needs to take the data in the BlogPostForm
object and find the Blog
in the database, and call AddBlogPost
:
IBlogRepository blogs = ...
Blog blog = blogs.Find(model.BlogId);
BlogPost post = blog.AddBlogPost(model.Title, model.Body, model.PublishDate.Value);
blogs.Save(blog);
Where should we put the logic of how to manipulate the Blog and BlogPost objects based on values in the BlogPostForm object, especially if this work includes more than a COMMIT
to a database?