[JAVA] Creating a sample program using the problem of a database specialist in DDD Improvement 2

Introduction

Like last time, it's a refactored article of creating a sample program in DDD using a database specialist issue.

I made a sample program using the problem of database specialist in Domain Driven Design Creating a sample program using the problem of a database specialist with DDD, improvement 1

This time, I refactored the part related to the "Deposit" endpoint.

The source code is here The version at the time of posting this article is tag 1.2.

Refactoring lottery winning decisions in multi-step lottery

In the first article posted

In a multi-stage lottery, determining the winning of the lottery from the outside of the entry causes the logic to leak to the Application, and above all, you will regret it later when the number of stages increases. It doesn't say that there are two stages, and if you increase it to two stages, you will be asked to increase it one more stage immediately.

I usually commented that. I tried this.

First, let's get the lottery result of the lottery participation application including the result of the multi-stage lottery, register it in the first class collection, and make it possible to get whether or not you have won from the first class collection. It was.

LotteryEntryResults.java


public class LotteryEntryResults {

  private List<LotteryEntryResult> list;

  public LotteryEntryResults(List<LotteryEntryResult> list) {
    this.list = list;
  }

  /**
   *Returns whether you have won.Returns true if winning.
   */
  public boolean winning() {

    for (LotteryEntryResult result : list) {
      if (result.lotteryResult == LotteryResult.winning) {
        return true;
      }
    }

    return false;
  }
}

MybatisLotteryEntryResultRepository.java


  @Override
  public LotteryEntryResults findLotteryEntryResults(
      FestivalId festivalId,
      MemberId memberId,
      EntryId entryId) {

    List<LotteryEntryResult> resultList = new ArrayList<>();

    EntryId targetEntryId = entryId;
    while (true) {
      LotteryEntryResult lotteryEntryResult = lotteryEntryResultMapper.selectLotteryEntryResult(
          festivalId, memberId, targetEntryId);
      if (lotteryEntryResult == null) {
        break;
      }

      resultList.add(lotteryEntryResult);

      EntryDto entryDto = entryMapper.selectEntry(festivalId, targetEntryId);
      EntryId followingEntryId = entryDto.followingEntryId();
      if (followingEntryId == null) {
        break;
      }
      targetEntryId = followingEntryId;
    }

    return new LotteryEntryResults(resultList);
  }

I was asked to try using recursive processing, but I had never done recursive processing with SQL, so this time I tried it muddy: sweat_smile:

As a result, it was possible to move the winning judgment of the lottery from the application layer to the domain layer in the multi-stage lottery. In addition, it is now possible to handle multi-stage lottery with 3 or more stages.

** Before refactoring **

PaymentCommandService.java



    Entry entry = entryRepository.findEntry(festivalId, application.entryId());
    if (entry.isLotteryEntry()) {
      //If the target entry is a lottery, check if it has won
      LotteryEntryResult entryResult = lotteryEntryResultRepository.findLotteryEntryResult(
          festivalId, memberId, entry.entryId());

      if (entryResult.lotteryResult() == LotteryResult.failed) {
        EntryId followingEntryId = ((LotteryEntry)entry).followingEntryId();
        if (followingEntryId == null) {
          throw new BusinessErrorException("I have not won the target tournament");
        } else {
          LotteryEntryResult followingEntryResult =
              lotteryEntryResultRepository.findLotteryEntryResult(
                  festivalId, memberId, followingEntryId);

          if (followingEntryResult.lotteryResult() == LotteryResult.failed) {
            throw new BusinessErrorException("I have not won the target tournament");
          }
        }
      }
    }

** After refactoring **

PaymentCommandService.java


    Entry entry = entryRepository.findEntry(festivalId, application.entryId());
    if (entry.isLotteryEntry()) {
      //If the target entry is a lottery, check if it has won
      LotteryEntryResults lotteryEntryResults =
          lotteryEntryResultRepository.findLotteryEntryResults(
              festivalId, memberId, entry.entryId());

      if (!lotteryEntryResults.winning()) {
        throw new BusinessErrorException("I have not won the target tournament");
      }
    }

Cleaner with fewer if statements: thumbs up:

Refactoring where points are used

Regarding the processing of point usage

I think it's good to make MemberPoints a first-class collection, but I get the impression that the inside of for is long. I thought that it would be cleaner to have MemberPoint itself, which is a collection member, in most of the memberPoints for.

I feel that the point balance is a method that should be given to the Point class. I also hard-code the expiration date with plusYears (1), but it seems better to have this in the Point class as well.

I received comments such as, and tried refactoring with reference to the comments I received.

** Before refactoring **

MemberPoints.java


public class MemberPoints {

  private List<MemberPoint> list;

  /**
   *Determine if the argument points can be used and use from the point near the expiration date.
   *In addition, change the state of the MemberPoint object to be retained.
   */
  public void usePoints(LocalDate paymentDate, PointAmount pointAmount) {

    //Use points from the points given so far until this value becomes zero
    BigDecimal x = pointAmount.value();

    for (MemberPoint memberPoint : list) {

      //Expiration date check
      LocalDate expirationDate = memberPoint.givenPointDate().plusYears(1);
      if (paymentDate.compareTo(expirationDate) > 0) {
        continue;
      }

      //Check the point balance
      BigDecimal availableUsePoint = memberPoint.givenPoint().value()
          .subtract(memberPoint.usedPoint().value());
      if (availableUsePoint.compareTo(BigDecimal.ZERO) == 0) {
        continue;
      }

      if (availableUsePoint.compareTo(x) <= 0) {
        memberPoint.use(availableUsePoint);
        x = x.subtract(availableUsePoint);
      } else {
        memberPoint.use(x);
        x = BigDecimal.ZERO;
        break;
      }
    }

    //Use points from the one with the closest expiration date, and if the number of points you want to use is not reached, an error will occur.
    if (x.compareTo(BigDecimal.ZERO) > 0) {
      throw new BusinessErrorException("Insufficient points");
    }
  }
}

** After refactoring **

MemberPoints.java


public class MemberPoints {

  private List<MemberPoint> list;

  /**
   *Determine if the argument points can be used and use from the point near the expiration date.
   *In addition, change the state of the MemberPoint object to be retained.
   */
  public void usePoints(LocalDate paymentDate, PointAmount usePointAmount) {

    PointAmount pointBalance = pointBalance(paymentDate);
    if (pointBalance.value().compareTo(usePointAmount.value()) < 0) {
      throw new BusinessErrorException("Insufficient points");
    }

    //Use points from the points given so far until this value becomes zero
    BigDecimal x = usePointAmount.value();

    for (MemberPoint memberPoint : list) {

      //Expiration date check
      if (memberPoint.hasPassedExpirationDate(paymentDate)) {
        continue;
      }

      //Check the point balance
      PointAmount availablePoint = memberPoint.availablePoint(paymentDate);
      if (!availablePoint.isPositive()) {
        continue;
      }

      if (availablePoint.value().compareTo(x) <= 0) {
        memberPoint.use(availablePoint);
        x = x.subtract(availablePoint.value());
      } else {
        memberPoint.use(new PointAmount(x));
        break;
      }
    }
  }

  private PointAmount pointBalance(LocalDate paymentDate) {

    PointAmount result = new PointAmount(BigDecimal.ZERO);

    for (MemberPoint memberPoint : list) {
      PointAmount availablePoint = memberPoint.availablePoint(paymentDate);
      result = result.add(availablePoint);
    }

    return result;
  }
}

MemberPoint.java


public class MemberPoint implements Entity {

  private MemberId memberId;
  private LocalDate givenPointDate;
  private PointAmount givenPoint;
  private PointAmount usedPoint;

  /**
   *Returns the number of points available on the target date specified by the argument.
   */
  PointAmount availablePoint(LocalDate targetDate) {

    if (targetDate.compareTo(expirationDate()) > 0) {
      return new PointAmount(BigDecimal.ZERO);
    }

    BigDecimal result = givenPoint.value().subtract(usedPoint.value());
    return new PointAmount(result);
  }

  /**
   *Return expiration date.
   */
  LocalDate expirationDate() {
    return givenPointDate.plusYears(1);
  }

  /**
   *Returns whether the expiration date has passed.Returns true if the expiration date has passed.
   */
  boolean hasPassedExpirationDate(LocalDate paymentDate) {

    return paymentDate.compareTo(expirationDate()) > 0;
  }
}

By creating a method that returns the number of points available to the MemberPoint class and a method that returns the expiration date and whether the expiration date has passed, it is possible to make the ʻusePoints method of the MemberPoints` class cleaner. It's done.

at the end

Rather than domain-driven design, it's more like object-oriented programming. Both are really difficult: expensive: Especially in my case, I only have tactical DDD, so I don't know the "bounded context" at all ... I hope someday I can also output strategic DDD ...

Also, there was another endpoint of "registering lottery results", but I couldn't think of a good idea, so I gave up this time. (I feel that the endpoint specifications were not good in the first place)

This time, I tried to make a sample program using this database specialist's problem, and I feel that my experience has improved. Also, I would like to challenge using the problems of another year and produce an output that has grown a little more than this time.

Thank you for reading this far. Comments from everyone I am very happy and have a lot to learn, so I would appreciate it if you could comment.

Recommended Posts

Creating a sample program using the problem of a database specialist in DDD Improvement 2
Creating a sample program using the problem of a database specialist in DDD Improvement 1
I tried to make a sample program using the problem of database specialist in Domain Driven Design
Sample program that returns the hash value of a file in Java
Using the database (SQL Server 2014) from a Java program 2018/01/04
A program that counts the number of words in a List
A memorandum of the FizzBuzz problem
Order of processing in the program
Creating a Servlet in the Liberty environment
I made a sample of how to write delegate in SwiftUI 2.0 using MapKit
Measure the size of a folder in Java
Sample code to assign a value in a property file to a field of the expected type
Count the number of occurrences of a string in Ruby
I tried using a database connection in Android development
About the problem of deadlock in parallel processing in gem'sprockets' 4.0
I wrote a sequence diagram of the j.u.c.Flow sample
[Ruby] Get in the habit of using the dup method when making a copy of a string variable
A program (Java) that outputs the sum of odd and even numbers in an array
Sample code to get the values of major SQL types in Java + Oracle Database 12c
Create a database of all the books that have been circulating in Japan in the last century
I made a program in Java that solves the traveling salesman problem with a genetic algorithm
Template creation program when using the reminder function in slack
A quick explanation of the five types of static in Java
Check the dependency of a specific maven artifact in Coursier
Determine that the value is a multiple of 〇 in Ruby
What is the representation of domain knowledge in the [DDD] model?
Procedure for publishing an application using AWS (4) Creating a database
[Ruby] Creating code using the concept of classes and instances
Explanation of Ruby on rails for beginners ③ ~ Creating a database ~
Find the number of days in a month with Kotlin