Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -34,6 +35,16 @@ public ResponseEntity<String> index(
return new ResponseEntity<>(HttpStatus.CREATED);
}

@PutMapping(value = "/courses/{id}/renameCourse")
public ResponseEntity<String> renameCourse(
@PathVariable String id,
@RequestBody Request request
) throws CommandHandlerExecutionError {
dispatch(new RenameCourseCommand(id, request.name()));

return new ResponseEntity<>(HttpStatus.OK);
}

@Override
public HashMap<Class<? extends DomainError>, HttpStatus> errorMapping() {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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;
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, EventBus eventBus) {
this.repository = repository;
this.eventBus = eventBus;
this.domainCourseFinder = new DomainCourseFinder(this.repository);
}

public void renameCourse(final CourseId courseId, final CourseName newCourseName) {
final Course course = domainCourseFinder.find(courseId);

this.repository.save(buildNewCourse(course, newCourseName));

this.eventBus.publish(course.pullDomainEvents());
}

private Course buildNewCourse(final Course course, final CourseName newCourseName) {
return Course.rename(course.id(), newCourseName, course.duration());
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<RenameCourseCommand> {
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);
}
}
7 changes: 7 additions & 0 deletions src/mooc/main/tv/codely/mooc/courses/domain/Course.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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();
}

}
Original file line number Diff line number Diff line change
@@ -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<String, Serializable> toPrimitives() {
return new HashMap<String, Serializable>() {{
put("name", name);
put("duration", duration);
}};
}

@Override
public CourseRenamedDomainEvent fromPrimitives(
String aggregateId,
HashMap<String, Serializable> 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);
}
}