Initial commit
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
package ch.unisg.tapastasks;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@SpringBootApplication
|
||||
public class TapasTasksApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
SpringApplication tapasTasksApp = new SpringApplication(TapasTasksApplication.class);
|
||||
tapasTasksApp.run(args);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package ch.unisg.tapastasks.common;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorFactory;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class SelfValidating<T> {
|
||||
|
||||
private Validator validator;
|
||||
|
||||
public SelfValidating() {
|
||||
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
|
||||
validator = factory.getValidator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates all Bean Validations on the attributes of this
|
||||
* instance.
|
||||
*/
|
||||
protected void validateSelf() {
|
||||
Set<ConstraintViolation<T>> violations = validator.validate((T) this);
|
||||
if (!violations.isEmpty()) {
|
||||
throw new ConstraintViolationException(violations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,42 @@
|
||||
package ch.unisg.tapastasks.tasks.adapter.in.web;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListCommand;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListUseCase;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import javax.validation.ConstraintViolationException;
|
||||
|
||||
@RestController
|
||||
public class AddNewTaskToTaskListWebController {
|
||||
private final AddNewTaskToTaskListUseCase addNewTaskToTaskListUseCase;
|
||||
|
||||
public AddNewTaskToTaskListWebController(AddNewTaskToTaskListUseCase addNewTaskToTaskListUseCase) {
|
||||
this.addNewTaskToTaskListUseCase = addNewTaskToTaskListUseCase;
|
||||
}
|
||||
|
||||
@PostMapping(path = "/tasks/", consumes = {TaskMediaType.TASK_MEDIA_TYPE})
|
||||
public ResponseEntity<String> addNewTaskTaskToTaskList(@RequestBody Task task) {
|
||||
try {
|
||||
AddNewTaskToTaskListCommand command = new AddNewTaskToTaskListCommand(
|
||||
task.getTaskName(), task.getTaskType()
|
||||
);
|
||||
|
||||
Task newTask = addNewTaskToTaskListUseCase.addNewTaskToTaskList(command);
|
||||
|
||||
// Add the content type as a response header
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskMediaType.TASK_MEDIA_TYPE);
|
||||
|
||||
return new ResponseEntity<>(TaskMediaType.serialize(newTask), responseHeaders, HttpStatus.CREATED);
|
||||
} catch (ConstraintViolationException e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
package ch.unisg.tapastasks.tasks.adapter.in.web;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.RetrieveTaskFromTaskListCommand;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.RetrieveTaskFromTaskListUseCase;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@RestController
|
||||
public class RetrieveTaskFromTaskListWebController {
|
||||
private final RetrieveTaskFromTaskListUseCase retrieveTaskFromTaskListUseCase;
|
||||
|
||||
public RetrieveTaskFromTaskListWebController(RetrieveTaskFromTaskListUseCase retrieveTaskFromTaskListUseCase) {
|
||||
this.retrieveTaskFromTaskListUseCase = retrieveTaskFromTaskListUseCase;
|
||||
}
|
||||
|
||||
@GetMapping(path = "/tasks/{taskId}")
|
||||
public ResponseEntity<String> retrieveTaskFromTaskList(@PathVariable("taskId") String taskId) {
|
||||
RetrieveTaskFromTaskListCommand command = new RetrieveTaskFromTaskListCommand(new Task.TaskId(taskId));
|
||||
Optional<Task> updatedTaskOpt = retrieveTaskFromTaskListUseCase.retrieveTaskFromTaskList(command);
|
||||
|
||||
// Check if the task with the given identifier exists
|
||||
if (updatedTaskOpt.isEmpty()) {
|
||||
// If not, through a 404 Not Found status code
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
// Add the content type as a response header
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskMediaType.TASK_MEDIA_TYPE);
|
||||
|
||||
return new ResponseEntity<>(TaskMediaType.serialize(updatedTaskOpt.get()), responseHeaders,
|
||||
HttpStatus.OK);
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package ch.unisg.tapastasks.tasks.adapter.in.web;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import ch.unisg.tapastasks.tasks.domain.TaskList;
|
||||
import org.json.JSONObject;
|
||||
|
||||
final public class TaskMediaType {
|
||||
public static final String TASK_MEDIA_TYPE = "application/json";
|
||||
|
||||
public static String serialize(Task task) {
|
||||
JSONObject payload = new JSONObject();
|
||||
|
||||
payload.put("taskId", task.getTaskId().getValue());
|
||||
payload.put("taskName", task.getTaskName().getValue());
|
||||
payload.put("taskType", task.getTaskType().getValue());
|
||||
payload.put("taskState", task.getTaskState().getValue());
|
||||
payload.put("taskListName", TaskList.getTapasTaskList().getTaskListName().getValue());
|
||||
|
||||
return payload.toString();
|
||||
}
|
||||
|
||||
private TaskMediaType() { }
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
package ch.unisg.tapastasks.tasks.adapter.out.web;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.application.port.out.NewTaskAddedEventPort;
|
||||
import ch.unisg.tapastasks.tasks.domain.NewTaskAddedEvent;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.HashMap;
|
||||
|
||||
@Component
|
||||
@Primary
|
||||
public class PublishNewTaskAddedEventWebAdapter implements NewTaskAddedEventPort {
|
||||
|
||||
//This is the base URI of the service interested in this event (in my setup, running locally as separate Spring Boot application)
|
||||
String server = "http://127.0.0.1:8082";
|
||||
|
||||
@Override
|
||||
public void publishNewTaskAddedEvent(NewTaskAddedEvent event) {
|
||||
|
||||
//Here we would need to work with DTOs in case the payload of calls becomes more complex
|
||||
|
||||
var values = new HashMap<String, String>() {{
|
||||
put("taskname",event.taskName);
|
||||
put("tasklist",event.taskListName);
|
||||
}};
|
||||
|
||||
var objectMapper = new ObjectMapper();
|
||||
String requestBody = null;
|
||||
try {
|
||||
requestBody = objectMapper.writeValueAsString(values);
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(server+"/roster/newtask/"))
|
||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.build();
|
||||
|
||||
/** Needs the other service running
|
||||
try {
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
**/
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package ch.unisg.tapastasks.tasks.application.port.in;
|
||||
|
||||
import ch.unisg.tapastasks.common.SelfValidating;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task.TaskType;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task.TaskName;
|
||||
import lombok.Value;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Value
|
||||
public class AddNewTaskToTaskListCommand extends SelfValidating<AddNewTaskToTaskListCommand> {
|
||||
@NotNull
|
||||
private final TaskName taskName;
|
||||
|
||||
@NotNull
|
||||
private final TaskType taskType;
|
||||
|
||||
public AddNewTaskToTaskListCommand(TaskName taskName, TaskType taskType) {
|
||||
this.taskName = taskName;
|
||||
this.taskType = taskType;
|
||||
this.validateSelf();
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
package ch.unisg.tapastasks.tasks.application.port.in;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
|
||||
public interface AddNewTaskToTaskListUseCase {
|
||||
Task addNewTaskToTaskList(AddNewTaskToTaskListCommand command);
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package ch.unisg.tapastasks.tasks.application.port.in;
|
||||
|
||||
import ch.unisg.tapastasks.common.SelfValidating;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task.TaskId;
|
||||
import lombok.Value;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Value
|
||||
public class RetrieveTaskFromTaskListCommand extends SelfValidating<RetrieveTaskFromTaskListCommand> {
|
||||
@NotNull
|
||||
private final TaskId taskId;
|
||||
|
||||
public RetrieveTaskFromTaskListCommand(TaskId taskId) {
|
||||
this.taskId = taskId;
|
||||
this.validateSelf();
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
package ch.unisg.tapastasks.tasks.application.port.in;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface RetrieveTaskFromTaskListUseCase {
|
||||
Optional<Task> retrieveTaskFromTaskList(RetrieveTaskFromTaskListCommand command);
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
package ch.unisg.tapastasks.tasks.application.port.out;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.domain.NewTaskAddedEvent;
|
||||
|
||||
public interface NewTaskAddedEventPort {
|
||||
|
||||
void publishNewTaskAddedEvent(NewTaskAddedEvent event);
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package ch.unisg.tapastasks.tasks.application.service;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListCommand;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListUseCase;
|
||||
import ch.unisg.tapastasks.tasks.application.port.out.NewTaskAddedEventPort;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.domain.NewTaskAddedEvent;
|
||||
import ch.unisg.tapastasks.tasks.domain.TaskList;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import javax.transaction.Transactional;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
@Transactional
|
||||
public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase {
|
||||
|
||||
private final NewTaskAddedEventPort newTaskAddedEventPort;
|
||||
|
||||
@Override
|
||||
public Task addNewTaskToTaskList(AddNewTaskToTaskListCommand command) {
|
||||
TaskList taskList = TaskList.getTapasTaskList();
|
||||
Task newTask = taskList.addNewTaskWithNameAndType(command.getTaskName(), command.getTaskType());
|
||||
|
||||
//Here we are using the application service to emit the domain event to the outside of the bounded context.
|
||||
//This event should be considered as a light-weight "integration event" to communicate with other services.
|
||||
//Domain events are usually rather "fat". In our implementation we simplify at this point. In general, it is
|
||||
//not recommended to emit a domain event via an application service! You should first emit the domain event in
|
||||
//the core and then the integration event in the application layer.
|
||||
if (newTask != null) {
|
||||
NewTaskAddedEvent newTaskAdded = new NewTaskAddedEvent(newTask.getTaskName().getValue(),
|
||||
taskList.getTaskListName().getValue());
|
||||
newTaskAddedEventPort.publishNewTaskAddedEvent(newTaskAdded);
|
||||
}
|
||||
|
||||
return newTask;
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package ch.unisg.tapastasks.tasks.application.service;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.RetrieveTaskFromTaskListCommand;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.RetrieveTaskFromTaskListUseCase;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import ch.unisg.tapastasks.tasks.domain.TaskList;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
import java.util.Optional;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
@Transactional
|
||||
public class RetrieveTaskFromTaskListService implements RetrieveTaskFromTaskListUseCase {
|
||||
@Override
|
||||
public Optional<Task> retrieveTaskFromTaskList(RetrieveTaskFromTaskListCommand command) {
|
||||
TaskList taskList = TaskList.getTapasTaskList();
|
||||
return taskList.retrieveTaskById(command.getTaskId());
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package ch.unisg.tapastasks.tasks.domain;
|
||||
|
||||
/**This is a domain event (these are usually much fatter)**/
|
||||
public class NewTaskAddedEvent {
|
||||
public String taskName;
|
||||
public String taskListName;
|
||||
|
||||
public NewTaskAddedEvent(String taskName, String taskListName) {
|
||||
this.taskName = taskName;
|
||||
this.taskListName = taskListName;
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
package ch.unisg.tapastasks.tasks.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**This is a domain entity**/
|
||||
public class Task {
|
||||
public enum State {
|
||||
OPEN, ASSIGNED, RUNNING, EXECUTED
|
||||
}
|
||||
|
||||
@Getter
|
||||
private final TaskId taskId;
|
||||
|
||||
@Getter
|
||||
private final TaskName taskName;
|
||||
|
||||
@Getter
|
||||
private final TaskType taskType;
|
||||
|
||||
@Getter
|
||||
private TaskState taskState;
|
||||
|
||||
public Task(TaskName taskName, TaskType taskType) {
|
||||
this.taskName = taskName;
|
||||
this.taskType = taskType;
|
||||
this.taskState = new TaskState(State.OPEN);
|
||||
this.taskId = new TaskId(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
protected static Task createTaskWithNameAndType(TaskName name, TaskType type) {
|
||||
//This is a simple debug message to see that the request has reached the right method in the core
|
||||
System.out.println("New Task: " + name.getValue() + " " + type.getValue());
|
||||
return new Task(name,type);
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class TaskId {
|
||||
private String value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class TaskName {
|
||||
private String value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class TaskState {
|
||||
private State value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class TaskType {
|
||||
private String value;
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
package ch.unisg.tapastasks.tasks.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
|
||||
import javax.swing.text.html.Option;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
/**This is our aggregate root**/
|
||||
public class TaskList {
|
||||
|
||||
@Getter
|
||||
private final TaskListName taskListName;
|
||||
|
||||
@Getter
|
||||
private final ListOfTasks listOfTasks;
|
||||
|
||||
//Note: We do not care about the management of task lists, there is only one within this service
|
||||
//--> using the Singleton pattern here to make lives easy; we will later load it from a repo
|
||||
//TODO change "tutors" to your group name ("groupx")
|
||||
private static final TaskList taskList = new TaskList(new TaskListName("tapas-tasks-tutors"));
|
||||
|
||||
private TaskList(TaskListName taskListName) {
|
||||
this.taskListName = taskListName;
|
||||
this.listOfTasks = new ListOfTasks(new LinkedList<Task>());
|
||||
}
|
||||
|
||||
public static TaskList getTapasTaskList() {
|
||||
return taskList;
|
||||
}
|
||||
|
||||
//Only the aggregate root is allowed to create new tasks and add them to the task list.
|
||||
//Note: Here we could add some sophisticated invariants/business rules that the aggregate root checks
|
||||
public Task addNewTaskWithNameAndType(Task.TaskName name, Task.TaskType type) {
|
||||
Task newTask = Task.createTaskWithNameAndType(name,type);
|
||||
listOfTasks.value.add(newTask);
|
||||
//This is a simple debug message to see that the task list is growing with each new request
|
||||
System.out.println("Number of tasks: "+listOfTasks.value.size());
|
||||
//Here we would also publish a domain event to other entities in the core interested in this event.
|
||||
//However, we skip this here as it makes the core even more complex (e.g., we have to implement a light-weight
|
||||
//domain event publisher and subscribers (see "Implementing Domain-Driven Design by V. Vernon, pp. 296ff).
|
||||
return newTask;
|
||||
}
|
||||
|
||||
public Optional<Task> retrieveTaskById(Task.TaskId id) {
|
||||
for (Task task : listOfTasks.value) {
|
||||
if (task.getTaskId().getValue().equalsIgnoreCase(id.getValue())) {
|
||||
return Optional.of(task);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class TaskListName {
|
||||
private String value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class ListOfTasks {
|
||||
private List<Task> value;
|
||||
}
|
||||
|
||||
}
|
1
tapas-tasks/src/main/resources/application.properties
Normal file
1
tapas-tasks/src/main/resources/application.properties
Normal file
@@ -0,0 +1 @@
|
||||
server.port=8081
|
Reference in New Issue
Block a user