Last time, I wrote an article that I tried to make a sample program with DDD using the problem of database specialist.
I made a sample program using the problem of database specialist in Domain Driven Design
Thank you to everyone who commented in the comments section and on twitter. There were many things that didn't go well, and I think it's important to improve and continue to output, so this article was the one I wanted to post a refactored article.
Of the three endpoints, "Apply for entry slot", "Register lottery results", and "Deposit", "Apply for entry slot" has been refactored this time.
The source code is here The version at the time of posting this article is tag 1.1.
I felt that modeling wasn't working well, so @ little_hand_s's [What is DDD modeling and how to put it into code](https://www.slideshare.net/koichiromatsuoka/domain-modeling- I created a domain model diagram with reference to the slide called andcoding).
The part corresponding to the business rule is blown out from the exam questions. All of these balloons are not related to the program of this entry point, but it was nice to be able to organize the logic to be written in the domain layer.
In the previous program, the part "Members can apply for participation in only one entry frame for one tournament" was leaked to the application layer, so I tried to move it to the domain layer.
To @ a-suenami in the previous comment section
For example, I think that the condition that "only one entry frame can be applied for one tournament" is a concern of the domain, so instead of using if in the application service, create a type like the tournament application acceptance policy (FestivalApplicationPolicy). I think it's better to put it in the domain model.
I received the opinion that I implemented this in my own way.
FestivalApplicationPolicy.java
public class FestivalApplicationPolicy {
private List<Application> applicationList;
public FestivalApplicationPolicy(List<Application> applicationList) {
this.applicationList = new ArrayList<>(applicationList);
}
/**
*Returns whether the member specified by the argument has already applied for participation in the tournament specified by the argument.
*Returns true if already applied.
*/
boolean hasAlreadyApplyForSameFestival(MemberId memberId, FestivalId festivalId) {
for (Application application : applicationList) {
if (application.festivalId().equals(festivalId)
&& application.memberId().equals(memberId)) {
return true;
}
}
return false;
}
}
I'm using a first class collection. I have a list of participation applications in the field, and if there are participation applications with the same membership number and tournament number, I tried to judge that participation application has already been completed.
I tried to use this in the domain service described later.
I need to create an entity to persist the application, and I created a domain service to create that entity. (The name is confusing, but I am creating it in the domain layer instead of the application layer) I'm still not sure how to use the domain service well, and I was wondering whether to create it with the static method in the entity of the participation application, but this time I did it like this.
ApplicationService.java
public class ApplicationService {
private Entry entry;
private FestivalApplicationPolicy festivalApplicationPolicy;
/**
*constructor.
*/
public ApplicationService(
Entry entry, FestivalApplicationPolicy festivalApplicationPolicy) {
this.entry = entry;
this.festivalApplicationPolicy = festivalApplicationPolicy;
}
/**
*Create and return a participation application object.
* @return Participation application object
*/
public Application createApplication(MemberId memberId, LocalDate applicationDate)
throws EntryStatusIsNotRecruitingException,
HasAlreadyApplyForSameFestivalException {
//Participation applications will be accepted only while the entry slot is open
if (entry.entryStatus() != EntryStatus.recruiting) {
throw new EntryStatusIsNotRecruitingException();
}
//Judgment based on the recruitment start date and recruitment end date should be made if the entry frame status is currently recruiting.
//Since no error should occur, I dare to throw an IllegalStateException here.
if (applicationDate.compareTo(entry.applicationStartDate()) < 0) {
throw new IllegalStateException("The specified tournament has not started recruiting yet");
}
if (applicationDate.compareTo(entry.applicationEndDate()) > 0) {
throw new IllegalStateException("The recruitment period for the designated tournament has passed");
}
//Members can apply for participation in only one entry slot for one tournament
if (festivalApplicationPolicy.hasAlreadyApplyForSameFestival(
memberId, entry.festivalId())) {
throw new HasAlreadyApplyForSameFestivalException();
}
return Application.createEntityForEntry(
entry.festivalId(),
memberId,
entry.entryId(),
applicationDate
);
}
}
After checking "Accept participation application only while the entry frame is being recruited", "Members can apply for participation only for one entry frame for one tournament", etc., if there is no error, the object of Application class to be persisted Is generated and returned.
We also create business exception classes ʻEntryStatusIsNotRecruitingException and
HasAlreadyApplyForSameFestivalException`. This was also given by @ a-suenami
I want to handle the message string itself to be shown to the user in the application service, but I think that what kind of error there is can be a domain object, so I want to return it as an enumeration type.
I tried it with reference to the comment. However, I'm using exceptions instead of enums. I was wondering if the business exception class field had an enumeration type that shows the type of error, but I thought that these two errors were also of interest to the participation application domain, so I created an exception class in the participation application domain package. This exception is caught in the application layer so that error messages can be handled.
The programs for application for participation in the previous and current application layers are as follows.
** Last time **
ApplicationCommandService.java
/**
*Apply for entry slot.
*/
public void applyForEntry(ApplyForEntryRequest request) {
final FestivalId festivalId = request.festivalId();
final MemberId memberId = request.memberId();
final EntryId entryId = request.entryId();
final LocalDate applicationDate = request.getApplicationDate();
final Member member = memberRepository.findMember(request.memberId());
if (member == null) {
throw new BusinessErrorException("It is a member that does not exist");
}
final Application alreadyApplication =
applicationRepository.findApplication(festivalId, memberId);
if (alreadyApplication != null) {
throw new BusinessErrorException("I have already applied for the specified tournament");
}
final Entry entry = entryRepository.findEntry(festivalId, entryId);
if (entry == null) {
throw new BusinessErrorException("It is an entry frame that does not exist");
}
final Application application = Application.createEntityForEntry(
festivalId,
memberId,
entryId,
applicationDate
);
entry.validateAndThrowBusinessErrorIfHasErrorForApplication(application);
entry.incrementApplicationNumbers();
entryRepository.saveEntry(entry);
applicationRepository.addApplication(application);
}
this time
ApplicationCommandService.java
/**
*Apply for entry slot.
*/
public void applyForEntry(ApplyForEntryRequest request) {
final FestivalId festivalId = request.festivalId();
final EntryId entryId = request.entryId();
final MemberId memberId = request.memberId();
final LocalDate applicationDate = request.getApplicationDate();
final Member member = memberRepository.findMember(memberId);
if (member == null) {
throw new BusinessErrorException("It is a member that does not exist");
}
final Entry entry = entryRepository.findEntry(festivalId, entryId);
if (entry == null) {
throw new BusinessErrorException("It is an entry frame that does not exist");
}
final FestivalApplicationPolicy festivalApplicationPolicy =
applicationRepository.createFestivalApplicationPolicy(festivalId, memberId);
final ApplicationService applicationService =
new ApplicationService(entry, festivalApplicationPolicy);
final Application application;
try {
application = applicationService.createApplication(memberId, applicationDate);
} catch (EntryStatusIsNotRecruitingException e) {
throw new BusinessErrorException("The designated tournament is not currently recruiting");
} catch (HasAlreadyApplyForSameFestivalException e) {
throw new BusinessErrorException("I have already applied for the specified tournament");
}
entry.incrementApplicationNumbers();
entryRepository.saveEntry(entry);
applicationRepository.addApplication(application);
}
Generate the domain service ʻApplicationService with the entry frame object ʻEntry
and the newly createdFestivalApplicationPolicy
as arguments, and generate the participation application entity ʻApplicaiton with ʻApplicationService
and make it persistent. I made it.
It looks like there isn't much change, but at least we were able to move "members can apply for only one entry slot for one tournament" to the domain layer.
In this case, it may not be very beneficial, but by repeating the improvement that brings the domain's interests to the domain layer, the program will be easy to change in actual business! !! It should be: sweat_smile: Also, by repeating improvements with refactoring, design skills should improve, so I will refactor the entry points of "register lottery results" and "deposit" in the near future and write an article.
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