skip to Main Content

I have an Article entity in my database:

public class Article
{
    public Guid Id { get; set; }

    public string Heading { get; set; }
    public string Author { get; set; }
    public string Content { get; set; }

    public DateTime CreatedOn { get; set; }
    public DateTime UpdatedOn { get; set; }

    public int ViewsCount { get; set; }

    public ImageData Image { get; set; }

    public IEnumerable<Comment> Comments { get; set; }
}

For the creation I have ArticleInputModel, and for displaying the details view, I have ArticleDetailsModel, and for update I have ArticleUpdateModel (etc….)

However those models have the same properties.

  • Should I separate this much if it means repetitions of code?
  • I try to follow SRP but this seems like is breaking DRY principle?
  • Am I overlooking something and what?

2

Answers


  1. Should I separate this much if it means repetitions of code?

    Usually, you can identify three situations with potentially different sets of properties when working with model classes (Data Transfer Objects; DTOs) for a single entity:

    1. entity creation
    2. entity reading (displaying, viewing)
    3. entity updating

    However, there may be many more subtypes — e.g. different ways to create or update an entity, partial vs. full update, various kinds of displays, e.g. full view, some kind of partial views, view of an entity in a list etc.

    It does make sense to have a system in constructing DTOs, such that you differentiate between the create, read (view), update DTOs in respect to your Create, Read, Update operations. You can see a clear parallel between such DTOs and CRU(D) operations (there’s typically no DTO for the Delete operation).

    Regardless of the particular naming you use, such categorizations help future maintainability of your code: if, in the future, you need to introduce a property that may not be set during entity creation, but can be altered during an update, or vice versa, it is easy to do without extensive changes to unrelated parts of code, e.g. you change the updating path only, but avoid changing the creating path.

    I try to follow SRP but this seems like is breaking DRY principle?

    Providing the model (DTOs) classes are semantically different, then I don’t see this as a violation of DRY. However, this may be subjective.

    Think of DTOs as secondary objects. The primary declaration is the database entity, which is part of your data model. The various views of such an entity in the form of DTOs are dependent on this entity declaration. As long as you keep it to a simple public SomeType PropName { get; set; } in the DTOs, it is not a violation of DRY you couldn’t live with. In addition, it makes sense to e.g. keep comments explaining various properties in entity declarations only, and not duplicate them into DTOs (unless you have to generate some API docs, but that’s solvable with <inheritdoc/> as well). What’s important, is the clear distinction between entities and DTOs and their roles.

    Login or Signup to reply.
  2. If you’re creating a new instance of an Article, what is it’s Id?

    Or as a more clear example, what will it’s UpdatedOn date be?

    How do you update something that doesn’t exist yet?

    One other issue you might come across very quickly is how are you going to return a list of all the articles by a particular Author?

    In the Article table you should be storing Author as an Id linking as a foreign key to the Author table (assuming there can only be a single Author).

    If your article table now looks like this…

    public class Article
    {
        public Guid Id { get; set; }
    
        public string Heading { get; set; }
        public Id Author { get; set; }
        public string Content { get; set; }
    
        public DateTime CreatedOn { get; set; }
        public DateTime UpdatedOn { get; set; }
    
        public int ViewsCount { get; set; }
    
        public ImageData Image { get; set; }
    
        public IEnumerable<Comment> Comments { get; set; }
    }
    

    …you might begin to see where separate ViewModels/DTOs come into play.

    1. Create

       public class CreateArticle
       {
           public string Heading { get; set; }
           public IEnumerable { get; set; }
           public string Content { get; set; }
           public string Image { get; set; }
       }
      

    You’re creating a new Article so will probably be inserting an auto generated Guid as the key. You’ll also be fairly likely to be taking the current date/time as the CreatedOn date. Author would come from a lookup list of some description so you’d need to pass some sort of list into the View (simplified as IEnumerable above). The image is most likely going to be supplied from a path to the image location so you’d maybe want to display as a text box.

    1. Add

       public class AddArticle
       {
           public string Heading { get; set; }
           public Id Author  { get; set; }
           public string Content { get; set; }     
           public ImageData Image { get; set; }    
      }
      

    When you’ve filled in your Create form, you now want to add it to the db. In this case your DTO needs to add data in the format the db expects. So you’d now be passing the selected Author Id and maybe the ImageData after some processing magic elsewhere.

    You still don’t need an Article Id or CreatedOn as these will be added once this DTO has validated.

    1. Details and View

    Hopefully you’re now seeing the slight differences that make the ViewModel a valuable asset. You might also require something like the following to show the details of an Article as opposed to viewing the Article itself:

    public class DetailOfArticle
    {
        public Guid Id { get; set; }
        public string Heading { get; set; }
        public Author Author { get; set; }
        public string Content { get; set; }
    
        public string CreatedOn { get; set; }
        public string UpdatedOn { get; set; }
    
        public int ViewsCount { get; set; }
    
    }
    
    public class ViewArticle
    {
        public Guid Id { get; set; }
        public string Heading { get; set; }
        public string Author { get; set; }
        public string Content { get; set; }
    
        public string CreatedOn { get; set; }
        public string UpdatedOn { get; set; }
    
        public int ViewsCount { get; set; }
    
        public ImageData Image { get; set; }    
        
        public IEnumerable<Comment> Comments { get; set; }
    }
    

    Notice that the details might pass in an Author entity so that you can supply more information (this could also be exploded out into separate properties). You might also want to pass the date (and/or time) as a string after formatting etc.

    The Article detail probably wouldn’t need the comments as it’s essentially the meta-data about the Article whereas the Article view is the Article as you’d want to present it for reading.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search