Search code examples
architecturecqrsclean-architecture

How to avoid almost repeatable model in clean architecture and make code follow DRY principle and dependency rule?


I building an e-commerce project using a clean architecture that has 4 layers as follows -

  1. Application layer
  2. UI Layer
  3. Domain layer
  4. Infrastrucure layer

Also using the CQRS pattern in the application layer. When something feature wants to add then it's required to create 3 similar kinds of models.

  1. Domain Layer - Entity - Entity model
  2. Application Layer - Model - create a model for command/ query
  3. UI Layer - VM - View Model

Due to the dependency rule of clean architecture, you cannot access the model from the lower layer. so need to create a similar kind of model from three different layers and its breaks the DRY (Don't Repeat Yourself) principle.

The second thing lot of code is required to write when adding new features. Let's say adding simple features following things need to create

  1. First create an entity in the domain layer
  2. Second create a command / Query (CQRS) in the application layer
  3. Third thing needs to require data for the command and then again create a request or response model for that create a model in the application layer
  4. Fourth create a view and show the data it requires again new model in the UI layer and requires another method for model mapping.

Please check folder structure using this Url - https://tree.nathanfriend.io/?s=(%27op2s!(%27fancyH~fullPath!false~trailYgSlashH~rootDotH)~W(%27W%27Solu23applica25Zfunc2s-8sKXK*InsRt8X7KQuRyK*GetAll8QuRy7KModelK*8Detailz93UI5ZF-ProductF7bModel-8VM7-6-j-q-0ErrorMsgKP_%20p_b-87html3DomaY5ZEntityxz940IsDisplay40IsBlock40Title3PRsistance5xCJ7%20%2F%2Fentity%20cJs-3%27)~vRsion!%271%27)*%20%20-Z*0*strYg%202tion3%5Cn*4K**5%20LayR6*Yt%20Id7.cs8Op29j4qFControllRH!trueJonfigura2K-*RerWsource!XCommandYinZ3*_agYa2bZViewj0Nameq0Typex-App8z7464%01zxqjb_ZYXWRKJHF987654320-*

And check following three models -

  1. OptionDetail.cs
  2. OptionVM.cs
  3. AppOption.cs

So I'm observing that I'm creating a similar kind of object in three different layers which have almost one or two property differences but the rest of the properties are similar.

So the problem is a lot of code require to write while adding simple feature and mostly create model and model mapping its take too much time.


Solution

  • It might be surprising to you, but the DRY principle does not mean remove all code that looks the same. David Thomas and Andrew Hunt explain the DRY principle in their book "The Pragmatic Programmer". They say that DRY is about knowledge duplication. In chapter 2, “A Pragmatic Approach,” p. 34, they provide this example:

    def validate_age(value):
        validate_type(value, :integer)
        validate_min_integer(value, 0)
    
    def validate_quantity(value):
        validate_type(value, :integer)
        validate_min_integer(value, 0)
    

    They say that:

    During code review, the resident know-it-all bounces this code, claming it's a DRY violation: both function bodies are the same.

    They are wrong. The code is the same, but the knowledge they represent is different. The two functions validate two separate things that just happen to have the same rules. That's coincidence, not a duplication.

    Uncle Bob describes a similar issue in chapter 7, “SRP The Single Responsibility Principle”, in his Clean Architecture book. The issues is described as "Symptom 1: Accidental Duplication". He says:

    For example, suppose that the calculatePay() function and the reportHours() function share a common algorithm for calculating non-overtime hours. Suppose that the developers, who are careful not to duplicate code, put that algorithm into a function named regularHours.

    Then he explains how changes from one actor, e.g. the CFO, can accidentally break code that the COO is interested in. The "duplication" in his example wasn't a real duplication. Like in the explanation of the DRY principle, it was coincidence, not a duplication.

    The objects you talked about in your question are responsible for different things. The entities in the inner most layer encapsulate use case agnostic business rules. The view models are responsible for how data is presented to the user. The request and response models are part of the API of the use case. Each model has a different purpose.

    You said that:

    So I'm observing that I'm creating a similar kind of object in three different layers which have almost one or two property differences but the rest of the properties are similar.

    So the problem is a lot of code require to write while adding simple feature and mostly create model and model mapping its take too much time.

    Just try to put all things together in the entity and you will see sooner or later what the problems are. Because putting everything together means that you will just have one object that is responsible for everything. You can even extend that object and also use it in the persistence layer. If you do that, you will find out fast that a change to the UI can easily break the persistence, and vice versa.

    Now we come to the annoying part. There are some use cases which tend to have very similar objects in all layers. So we tend to say "Puuh, it's a lot of work to do.", but we should make the effort in order to maintain the architecture.

    So my advice is to separate each responsibility.