From 9b2226bf60d42f71f79b41c003b4d3aa66d0e9c4 Mon Sep 17 00:00:00 2001 From: Xisco Bibiloni Date: Sat, 13 Aug 2022 12:49:39 +0200 Subject: [PATCH 1/8] feat:Create a new DomainCourseFinder to extract the common logic --- .../domain/service/DomainCourseFinder.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/mooc/main/tv/codely/mooc/courses/domain/service/DomainCourseFinder.java diff --git a/src/mooc/main/tv/codely/mooc/courses/domain/service/DomainCourseFinder.java b/src/mooc/main/tv/codely/mooc/courses/domain/service/DomainCourseFinder.java new file mode 100644 index 00000000..dc7469e6 --- /dev/null +++ b/src/mooc/main/tv/codely/mooc/courses/domain/service/DomainCourseFinder.java @@ -0,0 +1,20 @@ +package tv.codely.mooc.courses.domain.service; + +import tv.codely.mooc.courses.domain.Course; +import tv.codely.mooc.courses.domain.CourseId; +import tv.codely.mooc.courses.domain.CourseNotExist; +import tv.codely.mooc.courses.domain.CourseRepository; + +public class DomainCourseFinder { + private final CourseRepository courseRepository; + + + public DomainCourseFinder(CourseRepository courseRepository) { + this.courseRepository = courseRepository; + } + + public Course find(CourseId id) throws CourseNotExist { + return courseRepository.search(id) + .orElseThrow(() -> new CourseNotExist(id)); + } +} From 55e0876311977349778c2d8007141762816ac84e Mon Sep 17 00:00:00 2001 From: Xisco Bibiloni Date: Sat, 13 Aug 2022 14:47:15 +0200 Subject: [PATCH 2/8] feat:Refactor CourseFinder use case, extract code that the DomainCourseFinder should execute --- .../mooc/courses/application/find/CourseFinder.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/mooc/main/tv/codely/mooc/courses/application/find/CourseFinder.java b/src/mooc/main/tv/codely/mooc/courses/application/find/CourseFinder.java index b912a3e6..91a75a3e 100644 --- a/src/mooc/main/tv/codely/mooc/courses/application/find/CourseFinder.java +++ b/src/mooc/main/tv/codely/mooc/courses/application/find/CourseFinder.java @@ -4,19 +4,18 @@ import tv.codely.mooc.courses.domain.CourseId; import tv.codely.mooc.courses.domain.CourseNotExist; import tv.codely.mooc.courses.domain.CourseRepository; +import tv.codely.mooc.courses.domain.service.DomainCourseFinder; import tv.codely.shared.domain.Service; @Service public final class CourseFinder { - private final CourseRepository repository; + private final DomainCourseFinder domainCourseFinder; public CourseFinder(CourseRepository repository) { - this.repository = repository; + this.domainCourseFinder = new DomainCourseFinder(repository); } public CourseResponse find(CourseId id) throws CourseNotExist { - return repository.search(id) - .map(CourseResponse::fromAggregate) - .orElseThrow(() -> new CourseNotExist(id)); + return CourseResponse.fromAggregate(domainCourseFinder.find(id)); } } From cb28c73470ad900b1a97c3f0b654eab9994c3d84 Mon Sep 17 00:00:00 2001 From: Xisco Bibiloni Date: Sat, 13 Aug 2022 14:48:21 +0200 Subject: [PATCH 3/8] feat:Implement the new use case --> CourseNameUpdater --- .../application/update/CourseNameUpdater.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/mooc/main/tv/codely/mooc/courses/application/update/CourseNameUpdater.java diff --git a/src/mooc/main/tv/codely/mooc/courses/application/update/CourseNameUpdater.java b/src/mooc/main/tv/codely/mooc/courses/application/update/CourseNameUpdater.java new file mode 100644 index 00000000..009813e4 --- /dev/null +++ b/src/mooc/main/tv/codely/mooc/courses/application/update/CourseNameUpdater.java @@ -0,0 +1,30 @@ +package tv.codely.mooc.courses.application.update; + +import tv.codely.mooc.courses.domain.Course; +import tv.codely.mooc.courses.domain.CourseId; +import tv.codely.mooc.courses.domain.CourseName; +import tv.codely.mooc.courses.domain.CourseRepository; +import tv.codely.mooc.courses.domain.service.DomainCourseFinder; +import tv.codely.shared.domain.Service; + +@Service +public class CourseNameUpdater { + private final CourseRepository repository; + private final DomainCourseFinder domainCourseFinder; + + public CourseNameUpdater(CourseRepository repository, DomainCourseFinder domainCourseFinder) { + this.repository = repository; + this.domainCourseFinder = domainCourseFinder; + } + + public void renameCourse(final CourseId courseId, final CourseName newCourseName) { + final Course course = domainCourseFinder.find(courseId); + + this.repository.save(buildNewCourse(course, newCourseName)); + + } + + private Course buildNewCourse(final Course course, final CourseName newCourseName) { + return new Course(course.id(), newCourseName, course.duration()); + } +} From f6bfbbcee4c1a92c0a62c25bf6d21fcb5dd01587 Mon Sep 17 00:00:00 2001 From: Xisco Bibiloni Date: Sat, 13 Aug 2022 14:50:01 +0200 Subject: [PATCH 4/8] feat:Implement the command that controller should use --- .../update/RenameCourseCommand.java | 22 ++++++++++++++++++ .../update/RenameCourseCommandHandler.java | 23 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/mooc/main/tv/codely/mooc/courses/application/update/RenameCourseCommand.java create mode 100644 src/mooc/main/tv/codely/mooc/courses/application/update/RenameCourseCommandHandler.java diff --git a/src/mooc/main/tv/codely/mooc/courses/application/update/RenameCourseCommand.java b/src/mooc/main/tv/codely/mooc/courses/application/update/RenameCourseCommand.java new file mode 100644 index 00000000..1a0a1225 --- /dev/null +++ b/src/mooc/main/tv/codely/mooc/courses/application/update/RenameCourseCommand.java @@ -0,0 +1,22 @@ +package tv.codely.mooc.courses.application.update; + +import tv.codely.shared.domain.bus.command.Command; + +public class RenameCourseCommand implements Command { + private final String id; + + public RenameCourseCommand(String id, String name) { + this.id = id; + this.name = name; + } + + private final String name; + + public String id() { + return id; + } + + public String name() { + return name; + } +} diff --git a/src/mooc/main/tv/codely/mooc/courses/application/update/RenameCourseCommandHandler.java b/src/mooc/main/tv/codely/mooc/courses/application/update/RenameCourseCommandHandler.java new file mode 100644 index 00000000..f77b2a30 --- /dev/null +++ b/src/mooc/main/tv/codely/mooc/courses/application/update/RenameCourseCommandHandler.java @@ -0,0 +1,23 @@ +package tv.codely.mooc.courses.application.update; + +import tv.codely.mooc.courses.domain.CourseId; +import tv.codely.mooc.courses.domain.CourseName; +import tv.codely.shared.domain.Service; +import tv.codely.shared.domain.bus.command.CommandHandler; + +@Service +public final class RenameCourseCommandHandler implements CommandHandler { + private final CourseNameUpdater courseNameUpdater; + + public RenameCourseCommandHandler(final CourseNameUpdater courseNameUpdater) { + this.courseNameUpdater = courseNameUpdater; + } + + @Override + public void handle(final RenameCourseCommand command) { + CourseId id = new CourseId(command.id()); + CourseName name = new CourseName(command.name()); + + courseNameUpdater.renameCourse(id, name); + } +} From 1217bfe9e9dcf494612579cf4ae6fc264cbbf237 Mon Sep 17 00:00:00 2001 From: Xisco Bibiloni Date: Sat, 13 Aug 2022 14:50:50 +0200 Subject: [PATCH 5/8] feat:Create a new endpoint to use new use case --- .../controller/courses/CoursesPutController.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/main/tv/codely/apps/mooc/backend/controller/courses/CoursesPutController.java b/apps/main/tv/codely/apps/mooc/backend/controller/courses/CoursesPutController.java index 5c8b0171..b9a49731 100644 --- a/apps/main/tv/codely/apps/mooc/backend/controller/courses/CoursesPutController.java +++ b/apps/main/tv/codely/apps/mooc/backend/controller/courses/CoursesPutController.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import tv.codely.mooc.courses.application.create.CreateCourseCommand; +import tv.codely.mooc.courses.application.update.RenameCourseCommand; import tv.codely.shared.domain.DomainError; import tv.codely.shared.domain.bus.command.CommandBus; import tv.codely.shared.domain.bus.command.CommandHandlerExecutionError; @@ -34,6 +35,16 @@ public ResponseEntity index( return new ResponseEntity<>(HttpStatus.CREATED); } + @PutMapping(value = "/courses/{id}/renameCourse") + public ResponseEntity renameCourse( + @PathVariable String id, + @RequestBody Request request + ) throws CommandHandlerExecutionError { + dispatch(new RenameCourseCommand(id, request.name())); + + return new ResponseEntity<>(HttpStatus.OK); + } + @Override public HashMap, HttpStatus> errorMapping() { return null; From c203450406b2e92bc9ede404fb2f81d3857d0aa4 Mon Sep 17 00:00:00 2001 From: Xisco Bibiloni Date: Sat, 13 Aug 2022 15:01:10 +0200 Subject: [PATCH 6/8] feat:Create an instance but dependency injection could be used --- .../mooc/courses/application/update/CourseNameUpdater.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mooc/main/tv/codely/mooc/courses/application/update/CourseNameUpdater.java b/src/mooc/main/tv/codely/mooc/courses/application/update/CourseNameUpdater.java index 009813e4..ea508e2c 100644 --- a/src/mooc/main/tv/codely/mooc/courses/application/update/CourseNameUpdater.java +++ b/src/mooc/main/tv/codely/mooc/courses/application/update/CourseNameUpdater.java @@ -12,9 +12,9 @@ public class CourseNameUpdater { private final CourseRepository repository; private final DomainCourseFinder domainCourseFinder; - public CourseNameUpdater(CourseRepository repository, DomainCourseFinder domainCourseFinder) { + public CourseNameUpdater(CourseRepository repository) { this.repository = repository; - this.domainCourseFinder = domainCourseFinder; + this.domainCourseFinder = new DomainCourseFinder(this.repository); } public void renameCourse(final CourseId courseId, final CourseName newCourseName) { From 479293324e7f89304e046dfc4c332b3a7876df5f Mon Sep 17 00:00:00 2001 From: Xisco Bibiloni Date: Sun, 14 Aug 2022 19:18:14 +0200 Subject: [PATCH 7/8] feat:Implement changes in order to achieve: Domain model with VO and Event publish in use cases --- .../application/update/CourseNameUpdater.java | 8 +- .../tv/codely/mooc/courses/domain/Course.java | 7 ++ .../course/CourseRenamedDomainEvent.java | 94 +++++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 src/shared/main/tv/codely/shared/domain/course/CourseRenamedDomainEvent.java diff --git a/src/mooc/main/tv/codely/mooc/courses/application/update/CourseNameUpdater.java b/src/mooc/main/tv/codely/mooc/courses/application/update/CourseNameUpdater.java index ea508e2c..f64abcc4 100644 --- a/src/mooc/main/tv/codely/mooc/courses/application/update/CourseNameUpdater.java +++ b/src/mooc/main/tv/codely/mooc/courses/application/update/CourseNameUpdater.java @@ -6,14 +6,17 @@ import tv.codely.mooc.courses.domain.CourseRepository; import tv.codely.mooc.courses.domain.service.DomainCourseFinder; import tv.codely.shared.domain.Service; +import tv.codely.shared.domain.bus.event.EventBus; @Service public class CourseNameUpdater { private final CourseRepository repository; private final DomainCourseFinder domainCourseFinder; + private final EventBus eventBus; - public CourseNameUpdater(CourseRepository repository) { + public CourseNameUpdater(CourseRepository repository, EventBus eventBus) { this.repository = repository; + this.eventBus = eventBus; this.domainCourseFinder = new DomainCourseFinder(this.repository); } @@ -22,9 +25,10 @@ public void renameCourse(final CourseId courseId, final CourseName newCourseName this.repository.save(buildNewCourse(course, newCourseName)); + this.eventBus.publish(course.pullDomainEvents()); } private Course buildNewCourse(final Course course, final CourseName newCourseName) { - return new Course(course.id(), newCourseName, course.duration()); + return Course.rename(course.id(), newCourseName, course.duration()); } } diff --git a/src/mooc/main/tv/codely/mooc/courses/domain/Course.java b/src/mooc/main/tv/codely/mooc/courses/domain/Course.java index ef44a894..ea771567 100644 --- a/src/mooc/main/tv/codely/mooc/courses/domain/Course.java +++ b/src/mooc/main/tv/codely/mooc/courses/domain/Course.java @@ -2,6 +2,7 @@ import tv.codely.shared.domain.AggregateRoot; import tv.codely.shared.domain.course.CourseCreatedDomainEvent; +import tv.codely.shared.domain.course.CourseRenamedDomainEvent; import java.util.Objects; @@ -30,6 +31,12 @@ public static Course create(CourseId id, CourseName name, CourseDuration duratio return course; } + public static Course rename(final CourseId id, final CourseName name, final CourseDuration duration) { + final Course course = new Course(id, name, duration); + course.record(new CourseRenamedDomainEvent(id.value(), name.value(), duration.value())); + return course; + } + public CourseId id() { return id; } diff --git a/src/shared/main/tv/codely/shared/domain/course/CourseRenamedDomainEvent.java b/src/shared/main/tv/codely/shared/domain/course/CourseRenamedDomainEvent.java new file mode 100644 index 00000000..c90fb8e1 --- /dev/null +++ b/src/shared/main/tv/codely/shared/domain/course/CourseRenamedDomainEvent.java @@ -0,0 +1,94 @@ +package tv.codely.shared.domain.course; + +import tv.codely.shared.domain.bus.event.DomainEvent; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Objects; + +public final class CourseRenamedDomainEvent extends DomainEvent { + private final String name; + private final String duration; + + public CourseRenamedDomainEvent() { + super(null); + + this.name = null; + this.duration = null; + } + + public CourseRenamedDomainEvent(String aggregateId, String name, String duration) { + super(aggregateId); + + this.name = name; + this.duration = duration; + } + + public CourseRenamedDomainEvent( + String aggregateId, + String eventId, + String occurredOn, + String name, + String duration + ) { + super(aggregateId, eventId, occurredOn); + + this.name = name; + this.duration = duration; + } + + @Override + public String eventName() { + return "course.renamed"; + } + + @Override + public HashMap toPrimitives() { + return new HashMap() {{ + put("name", name); + put("duration", duration); + }}; + } + + @Override + public CourseRenamedDomainEvent fromPrimitives( + String aggregateId, + HashMap body, + String eventId, + String occurredOn + ) { + return new CourseRenamedDomainEvent( + aggregateId, + eventId, + occurredOn, + (String) body.get("name"), + (String) body.get("duration") + ); + } + + public String name() { + return name; + } + + public String duration() { + return duration; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CourseRenamedDomainEvent that = (CourseRenamedDomainEvent) o; + return name.equals(that.name) && + duration.equals(that.duration); + } + + @Override + public int hashCode() { + return Objects.hash(name, duration); + } +} From f1d001118e329f905bf44f967b6fc1b5bb1c2009 Mon Sep 17 00:00:00 2001 From: Xisco Bibiloni Date: Mon, 15 Aug 2022 11:10:10 +0200 Subject: [PATCH 8/8] feat:Add Unit tests of the new Use case: rename Course --- .../courses/CoursesModuleUnitTestCase.java | 4 ++ .../RenameCourseCommandHandlerTest.java | 62 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/mooc/test/tv/codely/mooc/courses/application/update/RenameCourseCommandHandlerTest.java diff --git a/src/mooc/test/tv/codely/mooc/courses/CoursesModuleUnitTestCase.java b/src/mooc/test/tv/codely/mooc/courses/CoursesModuleUnitTestCase.java index eb7454de..b3547bc8 100644 --- a/src/mooc/test/tv/codely/mooc/courses/CoursesModuleUnitTestCase.java +++ b/src/mooc/test/tv/codely/mooc/courses/CoursesModuleUnitTestCase.java @@ -20,4 +20,8 @@ protected void setUp() { public void shouldHaveSaved(Course course) { verify(repository, atLeastOnce()).save(course); } + + public void shouldNotHaveSaved() { + verify(repository, never()).save(any(Course.class)); + } } diff --git a/src/mooc/test/tv/codely/mooc/courses/application/update/RenameCourseCommandHandlerTest.java b/src/mooc/test/tv/codely/mooc/courses/application/update/RenameCourseCommandHandlerTest.java new file mode 100644 index 00000000..8237109f --- /dev/null +++ b/src/mooc/test/tv/codely/mooc/courses/application/update/RenameCourseCommandHandlerTest.java @@ -0,0 +1,62 @@ +package tv.codely.mooc.courses.application.update; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import tv.codely.mooc.courses.CoursesModuleUnitTestCase; +import tv.codely.mooc.courses.domain.Course; +import tv.codely.mooc.courses.domain.CourseMother; +import tv.codely.mooc.courses.domain.CourseName; +import tv.codely.mooc.courses.domain.CourseNotExist; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.when; + + +class RenameCourseCommandHandlerTest extends CoursesModuleUnitTestCase { + + private static final String NEW_NAME = "new name"; + private RenameCourseCommandHandler handler; + + @BeforeEach + protected void setUp() { + super.setUp(); + + handler = new RenameCourseCommandHandler(new CourseNameUpdater(repository, eventBus)); + } + + @Test + @DisplayName("Should rename course correctly when exist the course") + void should_rename_course_correctly_when_exist_the_course() { + // Arrange + final Course courseMock = CourseMother.random(); + final RenameCourseCommand renameCourseCommand = new RenameCourseCommand(courseMock.id().value(), NEW_NAME); + final Course courseExpected = Course.rename(courseMock.id(), new CourseName(NEW_NAME), courseMock.duration()); + + when(super.repository.search(courseMock.id())).thenReturn(Optional.of(courseMock)); + + // Action + + handler.handle(renameCourseCommand); + + // Assert + shouldHaveSaved(courseExpected); + } + + @Test + @DisplayName("Should return error when rename course but it not exist") + void should_return_error_when_rename_course_but_it_not_exist() { + // Arrange + final Course courseMock = CourseMother.random(); + final RenameCourseCommand renameCourseCommand = new RenameCourseCommand(courseMock.id().value(), NEW_NAME); + + // Action + assertThatThrownBy(() -> handler.handle(renameCourseCommand)).isInstanceOf(CourseNotExist.class); + + // Assert + shouldNotHaveSaved(); + } + +}