Initial commit

This commit is contained in:
Andrei Ciortea
2021-10-04 09:13:00 +02:00
commit 5b835bc6bc
36 changed files with 1662 additions and 0 deletions

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}

View File

@@ -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() { }
}

View File

@@ -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();
}
**/
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1 @@
server.port=8081

View File

@@ -0,0 +1,13 @@
package ch.unisg.tapastasks;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class TapasTasksApplicationTests {
@Test
void contextLoads() {
}
}