Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arquitectura Hexagonal Servicios de aplicacion vs servicios de dominio #78

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
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);
}
}