[JAVA] What is the representation of domain knowledge in the [DDD] model?

DDD serialized article

Overview

What does Eric Evans say about the definition of DDD In this article I wrote that the definition of Domain Driven Development looks like this:

  1. Focus on the core complexity and opportunities of your domain
  1. Explore models in collaboration with domain and software experts
  2. Write software that explicitly represents those models
  3. Speak in a ubiquitous language in a bounded context

So how is the verbalized model of domain knowledge ultimately represented in code?

Invariant

First, when we think about business constraints, we think about *** invariants ***.

Invariant: A condition that must always be consistent for the life of the model.

There are various names such as specifications and constraints, but the term invariant is used as the term for DDD, so we will unify it.

A model that does not represent domain knowledge

For example, suppose you want to model a task in a business application. After defining the requirements, I found that the following invariant conditions must be met.

Let's implement this.

@Entity
public class Task {
    @Id
    @GeneratedValue
    private Long id;
    private TaskStatus taskStatus;
    private String name;
    private LocalDate dueDate;
    private int postponeCount;
    public static final int POSTPONE_MAX_COUNT = 3;

    public Task() {
    }

    /*
     *Setter for all items
     */
    public void setId(Long id) { this.id = id; }
    public void setTaskStatus(TaskStatus taskStatus) { this.taskStatus = taskStatus; }
    public void setName(String name) { this.name = name; }
    public void setDueDate(LocalDate dueDate) { this.dueDate = dueDate; }
    public void setPostponeCount(int postponeCount) { this.postponeCount = postponeCount; }

    /*
     *Getter for all items
     */
    public Long getId() { return this.id; }
    public String getName() { return this.name; }
    public TaskStatus getTaskStatus() { return this.taskStatus; }
    public LocalDate getDueDate() { return this.dueDate; }
    public int getPostponeCount() { return this.postponeCount; }
    
    /*
     *A method that barely has behavior
     */
    public boolean canPostpone() { return this.postponeCount < POSTPONE_MAX_COUNT; }
}
public enum TaskStatus {
    UNDONE, DONE
}
public class TaskApplication {
    @Autowired
    private TaskRepository taskRepository;
    
    public void createTask(String name, LocalDate dueDate) {
        if (name == null || dueDate == null) {
            throw new IllegalArgumentException("Required items are not set");
        }
        Task task = new Task();
        task.setTaskStatus(TaskStatus.UNDONE);
        task.setName(name);
        task.setDueDate(dueDate);
        task.setPostponeCount(0);
        taskRepository.save(task);
    }
    
    public void postponeTask(Long taskId) {
        Task task = taskRepository.findById(taskId);
        if (!taks.canPostpone()) {   	
            throw new IllegalArgumentException("The maximum number of postponements has been exceeded");
        }
        task.setDueDate(task.getDueDate().plusDays(1L));
        task.setPostponeCount(task.getPostponeCount() + 1);
        taskRepository.save(task);
    }
    //Completion processing is omitted
}

It's done! It looks like it meets the requirements. As far as the TaskApplication class is seen, there seems to be no problem in creating and updating Tasks. Let's release it now.

However, a few weeks after the release, an engineer who was not familiar with the business implemented the following code.

public class AnotherTaskApplication {
    @Autowired
    private TaskRepository taskRepository;
    
    public void createDoneTask(String name, LocalDate dueDate) {
        Task task = new Task();
        task.setTaskStatus(TaskStatus.DONE);  //× Task generation in completed state
        task.setPostponeCount(-1); //× The count is a minus
        taskRepository.save(task);
    }

    public void changeTask(Long taskId, String name, LocalDate dueDate, TaskStatus taskStatus) {
        Task task = taskRepository.findById(taskId);
        task.setName(name); //× Change the task name that should not be changed
        task.setDueDate(dueDate); //× Set the due date arbitrarily with the input value, ignore the number of postponements
        task.setTaskStatu(taskStatus); //× Tasks can be returned to incomplete
        taskRepository.save(task);     }
}

It's amazing, I was able to destroy the invariants.

The trouble is that the AnotherTaskAPplication class is created and implemented, so the person who implemented the original TaskApplication class did not notice that such processing was written in another class. (Let's leave the review system for now)

In this example, the specification is expressed by Application, but the Task model itself, which has Setter / Getter in all items, cannot express anything.

This condition is called *** domain model anemia ***.

This object, which cannot represent invariants, is no longer a model, but just a data model *** that projects a relational model onto the object. And such an application can be called *** transaction script ***.

So how can we get the model to express invariants?

A model that expresses domain knowledge

@Entity
class Task {
    @Id
    @GeneratedValue
    private Long id;
    private TaskStatus taskStatus;
    private String name;
    private LocalDate dueDate;
    private int postponeCount;
    private static final int POSTPONE_MAX_COUNT = 3;

    /*
     *Constructor: Represents an invariant when creating an entity
     */
    public Task(String name, LocalDate dueDate) {
        if (name == null || dueDate == null) {
            throw new IllegalArgumentException("Required items are not set");
        }
        this.name = name;
        this.dueDate = dueDate;
        this.taskStatus = TaskStatus.UNDONE;
        this.postponeCount = 0;
    }

    /*
     *State transition method: Represents an invariant related to the state transition of a created entity
     */
    public void postpone() {
        if (postponeCount >= POSTPONE_MAX_COUNT) {
            throw new IllegalArgumentException("The maximum number of postponements has been exceeded");
        }
        dueDate.plusDays(1L);
        postponeCount++;
    }
    public void done() {
        this.taskStatus = TaskStatus.DONE;
    }
    
    //The name setter doesn't exist, so you can't change the name

    /*
     *getter, state acquisition method
     */
    public Long getId() { return id; }
    public String getName() { return name; }
    public LocalDate getDueDate() { return dueDate; }
    public boolean isUndone() { return this.taskStatus == TaskStatus.UNDONE; }
    public boolean canPostpone() { return this.postponeCount < POSTPONE_MAX_COUNT; }
}

//In the same package, enum is defined as package private
enum TaskStatus {
    UNDONE, DONE
}
public class TaskApplication {
    @Autowired
    private TaskRepository taskRepository;

    public void createTask(String name, LocalDate dueDate) {
        Task task = new Task(name, dueDate); //The task instance is always created in a form that satisfies the invariant condition.
        taskRepository.save(task);
    }

    public void postpone(Long taskId) {
        Task task = taskRepository.findById(taskId);
        task.postpone(); //Processing that destroys invariants cannot be written from application for the acquired object.
        taskRepository.save(task);
    }
    //Completion processing is omitted
    //Validation is omitted
}

When the process of imposing invariants was transferred to the model ... A very expressive model was created! !!

The invariant condition is beautifully expressed by the constructor of the Task model and the state transition method.

Some of the benefits of this design include:

It's a good thing.

If you write this, you can say that *** the model expresses the knowledge of the domain ***.

Looking back on the definition

Let's look back at the definition of Domain Driven.

  1. Focus on the core complexity and opportunities of your domain
  1. Explore models in collaboration with domain and software experts
  2. Write software that explicitly represents those models
  3. Speak in a ubiquitous language in a bounded context

In the above example, I was able to write software that represented the model.

This design alone has had great benefits, but Domain Driven doesn't end there.
What kind of behavior does such a model behave, and how will it change when the specifications are changed? By matching the definition of words with business (domain) experts, we always pursue the latest and more expressive model and follow the code.

This is literally *** domain model driven development ***.

I hope you read this article and think, "Oh, maybe DDD?" I will continue to write articles to spread DDD in the future, so please keep in touch with me if you like.

Recommended Posts

What is the representation of domain knowledge in the [DDD] model?
What is the difference between the responsibilities of the domain layer and the application layer in the onion architecture [DDD]
'% 02d' What is the percentage of% 2?
What is @Override or @SuppressWarnings ("SleepWhileInLoop") in front of the function? ?? ??
What is testing? ・ About the importance of testing
What is the model test code testing
What is the main method in Java?
What is the data structure of ActionText?
What is Pullback doing in The Composable Architecture
What is JSP? ~ Let's know the basics of JSP !! ~
What is the Java Servlet / JSP MVC model?
Ebean.update () is not executed in the inherited model.
Retrieve the first day of week in current locale (what day of the week is it today?)
What is MVC model?
In Time.strptime,% j (total date of the year) is
What Is Domain Driven Design Trying to Solve [DDD]
[DDD] What is the most accessible architecture to get started with Domain Driven Design?
What is the pluck method?
Review the basic knowledge of ruby that is often forgotten
Determine that the value is a multiple of 〇 in Ruby
What is the BufferedReader class?
What is the constructor for?
What is the initialize method?
[Note] What to do if bundle install in Chapter 3 of the rails tutorial is not possible
The story that a model is not a "correct representation of the real world" / the need for a bounded context
Want to know what Ruby n is the power of 2? (Power judgment of 2)
What to do if Cloud9 is full in the Rails tutorial
The milliseconds to set in /lib/calendars.properties of Java jre is UTC
Problem that the attribute of User model becomes nil in ActionMailer
[Reference example] Copyright is described in the comment of the source code.
What is a snippet in programming?
What is it? ~ 3 types of "no" ~
What is @Autowired in Spring boot?
Order of processing in the program
What kind of method is define_method?
What are the rules in JUnit?
What you are doing in the confirmation at the time of gem update
What to do if the prefix c is not bound in JSP
Code of the part where server.servlet.session.timeout is set in spring.session.timeout in spring-boot2 system
What wasn't fair use in the diversion of Java APIs on Android
Where is the home directory location of "Ubuntu"? (Solved in the comment section)
Error when the member of Entity class used in SpringWebFlux is final
[Git] The horrifying story of deleting the master branch ~ The answer is in English ~
Review of "strange Java" and Java knowledge that is often forgotten in Java Bronze
What is CHECKSTYLE: OFF found in the Java source? Checkstyle to know from
[Beginner] What is Docker in the first place? Easy-to-understand explanation from the basics!
What is a class in Java language (3 /?)
Get the result of POST in Java
Spring Autowired is written in the constructor
What is Swift? Findings obtained in 3 weeks
What is the right migration data type? ??
[Technical memo] What is "include" in Ruby?
The identity of params [: id] in rails
What is the best file reading (Java)
What is a class in Java language (1 /?)
What is a class in Java language (2 /?)
The story of AppClip support in Anyca
What are the updated features of java 13
What is the Facade pattern useful for?
What is gem'foge', require: false in Gemfile?
The story of writing Java in Emacs