From fdb7d2bf6403c1715e0a994e75bcd959794e702e Mon Sep 17 00:00:00 2001 From: Ronny Seiger Date: Thu, 11 Nov 2021 11:03:53 +0100 Subject: [PATCH 1/9] Update build-and-deploy.yml Removing old containers during deploy --- .github/workflows/build-and-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 2c5b192..ee925ca 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -78,6 +78,7 @@ jobs: cd /home/${{ secrets.SSH_USER }}/ touch acme.json sudo chmod 0600 acme.json + sudo docker-compose down --remove-orphans sudo echo "PUB_IP=$(wget -qO- http://ipecho.net/plain | xargs echo)" | sed -e 's/\./-/g' > .env sudo docker-compose up -d From 32ecd6a5d4a2b00f16444dec77c92e4c1c53ae7e Mon Sep 17 00:00:00 2001 From: ronsei Date: Thu, 11 Nov 2021 11:32:10 +0100 Subject: [PATCH 2/9] Adding a MongoDB repository and extending the use cases for adding new tasks and retrieval of a new task --- docker-compose.yml | 33 ++++++++++++++++++ tapas-tasks/pom.xml | 9 +++++ .../tapastasks/TapasTasksApplication.java | 6 ++++ .../mongodb/MongoTaskDocument.java | 32 +++++++++++++++++ .../out/persistence/mongodb/TaskMapper.java | 30 ++++++++++++++++ .../mongodb/TaskPersistenceAdapter.java | 34 +++++++++++++++++++ .../persistence/mongodb/TaskRepository.java | 14 ++++++++ .../application/port/out/AddTaskPort.java | 9 +++++ .../application/port/out/LoadTaskPort.java | 10 ++++++ .../application/port/out/TaskListLock.java | 11 ++++++ .../service/AddNewTaskToTaskListService.java | 8 +++++ .../application/service/NoOpTaskListLock.java | 18 ++++++++++ .../RetrieveTaskFromTaskListService.java | 11 +++++- .../unisg/tapastasks/tasks/domain/Task.java | 26 ++++++++++++-- .../src/main/resources/application.properties | 5 +++ 15 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/MongoTaskDocument.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskMapper.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapter.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskRepository.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/AddTaskPort.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/LoadTaskPort.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/TaskListLock.java create mode 100644 tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/NoOpTaskListLock.java diff --git a/docker-compose.yml b/docker-compose.yml index 0081933..e69f3c1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -70,3 +70,36 @@ services: - "traefik.http.routers.app.tls=true" - "traefik.http.routers.app.entryPoints=web,websecure" - "traefik.http.routers.app.tls.certresolver=le" + + mongodb: + image: mongo + container_name: mongodb + restart: unless-stopped + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: 8nP7s0a # Can not be changed again later on + volumes: + - database:/data/db + + dbadmin: + image: mongo-express + container_name: dbadmin + restart: unless-stopped + environment: + ME_CONFIG_BASICAUTH_USERNAME: student # Access to web interface: username + ME_CONFIG_BASICAUTH_PASSWORD: studious # Access to web interface: password + ME_CONFIG_MONGODB_ADMINUSERNAME: root + ME_CONFIG_MONGODB_ADMINPASSWORD: 8nP7s0a # must correspond to the db + ME_CONFIG_MONGODB_PORT: 27017 # Default 27017 + ME_CONFIG_MONGODB_SERVER: mongodb + labels: + - "traefik.enable=true" + - "traefik.http.routers.dbadmin.rule=Host(`dbadmin1.${PUB_IP}.nip.io`)" + - "traefik.http.routers.dbadmin.service=dbadmin" + - "traefik.http.services.dbadmin.loadbalancer.server.port=8081" + - "traefik.http.routers.dbadmin.tls=true" + - "traefik.http.routers.dbadmin.entryPoints=web,websecure" + - "traefik.http.routers.dbadmin.tls.certresolver=le" + +volumes: + database: diff --git a/tapas-tasks/pom.xml b/tapas-tasks/pom.xml index 3eac732..7dcf6ae 100644 --- a/tapas-tasks/pom.xml +++ b/tapas-tasks/pom.xml @@ -27,6 +27,15 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.data + spring-data-mongodb + 3.2.6 + + + org.springframework.boot + spring-boot-starter-data-mongodb + org.projectlombok diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java index 2675391..78a6145 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java @@ -1,14 +1,20 @@ package ch.unisg.tapastasks; +import ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb.TaskRepository; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; @SpringBootApplication +@EnableMongoRepositories(basePackageClasses = TaskRepository.class) public class TapasTasksApplication { public static void main(String[] args) { SpringApplication tapasTasksApp = new SpringApplication(TapasTasksApplication.class); tapasTasksApp.run(args); + + + } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/MongoTaskDocument.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/MongoTaskDocument.java new file mode 100644 index 0000000..442e01e --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/MongoTaskDocument.java @@ -0,0 +1,32 @@ +package ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb; + +import lombok.Data; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Data +@Document(collection = "tasks") +public class MongoTaskDocument { + + @Id + public String taskId; + + public String taskName; + public String taskType; + public String originalTaskUri; + public String taskStatus; + public String taskListName; + + + public MongoTaskDocument(String taskId, String taskName, String taskType, + String originalTaskUri, + String taskStatus, String taskListName) { + + this.taskId = taskId; + this.taskName = taskName; + this.taskType = taskType; + this.originalTaskUri = originalTaskUri; + this.taskStatus = taskStatus; + this.taskListName = taskListName; + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskMapper.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskMapper.java new file mode 100644 index 0000000..0af73b6 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskMapper.java @@ -0,0 +1,30 @@ +package ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb; + +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import org.springframework.stereotype.Component; + +@Component +class TaskMapper { + + Task mapToDomainEntity(MongoTaskDocument task) { + return Task.withIdNameTypeOriginaluriStatus( + new Task.TaskId(task.taskId), + new Task.TaskName(task.taskName), + new Task.TaskType(task.taskType), + new Task.OriginalTaskUri(task.originalTaskUri), + new Task.TaskStatus(Task.Status.valueOf(task.taskStatus)) + ); + } + + MongoTaskDocument mapToMongoDocument(Task task) { + return new MongoTaskDocument( + task.getTaskId().getValue(), + task.getTaskName().getValue(), + task.getTaskType().getValue(), + task.getOriginalTaskUri().getValue(), + task.getTaskStatus().getValue().toString(), + TaskList.getTapasTaskList().getTaskListName().getValue() + ); + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapter.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapter.java new file mode 100644 index 0000000..beabf63 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapter.java @@ -0,0 +1,34 @@ +package ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb; + +import ch.unisg.tapastasks.tasks.application.port.out.AddTaskPort; +import ch.unisg.tapastasks.tasks.application.port.out.LoadTaskPort; +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class TaskPersistenceAdapter implements + AddTaskPort, + LoadTaskPort { + + @Autowired + private final TaskRepository taskRepository; + + private final TaskMapper taskMapper; + + @Override + public void addTask(Task task) { + MongoTaskDocument mongoTaskDocument = taskMapper.mapToMongoDocument(task); + taskRepository.save(mongoTaskDocument); + } + + @Override + public Task loadTask(Task.TaskId taskId, TaskList.TaskListName taskListName) { + MongoTaskDocument mongoTaskDocument = taskRepository.findByTaskId(taskId.getValue(),taskListName.getValue()); + Task task = taskMapper.mapToDomainEntity(mongoTaskDocument); + return task; + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskRepository.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskRepository.java new file mode 100644 index 0000000..867671c --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskRepository.java @@ -0,0 +1,14 @@ +package ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface TaskRepository extends MongoRepository { + + public MongoTaskDocument findByTaskId(String taskId, String taskListName); + + public List findByTaskListName(String taskListName); +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/AddTaskPort.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/AddTaskPort.java new file mode 100644 index 0000000..b87795d --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/AddTaskPort.java @@ -0,0 +1,9 @@ +package ch.unisg.tapastasks.tasks.application.port.out; + +import ch.unisg.tapastasks.tasks.domain.Task; + +public interface AddTaskPort { + + void addTask(Task task); + +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/LoadTaskPort.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/LoadTaskPort.java new file mode 100644 index 0000000..acca3c0 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/LoadTaskPort.java @@ -0,0 +1,10 @@ +package ch.unisg.tapastasks.tasks.application.port.out; + +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskList; + +public interface LoadTaskPort { + + Task loadTask(Task.TaskId taskId, TaskList.TaskListName taskListName); + +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/TaskListLock.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/TaskListLock.java new file mode 100644 index 0000000..802abba --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/TaskListLock.java @@ -0,0 +1,11 @@ +package ch.unisg.tapastasks.tasks.application.port.out; + +import ch.unisg.tapastasks.tasks.domain.TaskList; + +public interface TaskListLock { + + void lockTaskList(TaskList.TaskListName taskListName); + + void releaseTaskList(TaskList.TaskListName taskListName); + +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java index 2380fcf..d78de83 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java @@ -2,7 +2,9 @@ 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.AddTaskPort; import ch.unisg.tapastasks.tasks.application.port.out.NewTaskAddedEventPort; +import ch.unisg.tapastasks.tasks.application.port.out.TaskListLock; import ch.unisg.tapastasks.tasks.domain.Task; import ch.unisg.tapastasks.tasks.domain.NewTaskAddedEvent; @@ -17,11 +19,14 @@ import javax.transaction.Transactional; public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase { private final NewTaskAddedEventPort newTaskAddedEventPort; + private final AddTaskPort addTaskToRepositoryPort; + private final TaskListLock taskListLock; @Override public Task addNewTaskToTaskList(AddNewTaskToTaskListCommand command) { TaskList taskList = TaskList.getTapasTaskList(); + taskListLock.lockTaskList(taskList.getTaskListName()); Task newTask = (command.getOriginalTaskUri().isPresent()) ? // Create a delegated task that points back to the original task taskList.addNewTaskWithNameAndTypeAndOriginalTaskUri(command.getTaskName(), @@ -29,6 +34,9 @@ public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase // Create an original task : taskList.addNewTaskWithNameAndType(command.getTaskName(), command.getTaskType()); + addTaskToRepositoryPort.addTask(newTask); + taskListLock.releaseTaskList(taskList.getTaskListName()); + //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 diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/NoOpTaskListLock.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/NoOpTaskListLock.java new file mode 100644 index 0000000..783dca1 --- /dev/null +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/NoOpTaskListLock.java @@ -0,0 +1,18 @@ +package ch.unisg.tapastasks.tasks.application.service; + +import ch.unisg.tapastasks.tasks.application.port.out.TaskListLock; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import org.springframework.stereotype.Component; + +@Component +public class NoOpTaskListLock implements TaskListLock { + @Override + public void lockTaskList(TaskList.TaskListName taskListName) { + //do nothing + } + + @Override + public void releaseTaskList(TaskList.TaskListName taskListName) { + //do nothing + } +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java index fd6aea5..5e5ec29 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java @@ -2,6 +2,7 @@ package ch.unisg.tapastasks.tasks.application.service; import ch.unisg.tapastasks.tasks.application.port.in.RetrieveTaskFromTaskListQuery; import ch.unisg.tapastasks.tasks.application.port.in.RetrieveTaskFromTaskListUseCase; +import ch.unisg.tapastasks.tasks.application.port.out.LoadTaskPort; import ch.unisg.tapastasks.tasks.domain.Task; import ch.unisg.tapastasks.tasks.domain.TaskList; import lombok.RequiredArgsConstructor; @@ -14,9 +15,17 @@ import java.util.Optional; @Component @Transactional public class RetrieveTaskFromTaskListService implements RetrieveTaskFromTaskListUseCase { + + private final LoadTaskPort loadTaskFromRepositoryPort; + @Override public Optional retrieveTaskFromTaskList(RetrieveTaskFromTaskListQuery query) { TaskList taskList = TaskList.getTapasTaskList(); - return taskList.retrieveTaskById(query.getTaskId()); + + Optional task = taskList.retrieveTaskById(query.getTaskId()); + + Optional taskFromRepo = Optional.ofNullable(loadTaskFromRepositoryPort.loadTask(query.getTaskId(), taskList.getTaskListName())); + + return taskFromRepo; } } diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java index b664a64..7a02976 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/domain/Task.java @@ -49,17 +49,39 @@ public class Task { this.outputData = null; } - protected static Task createTaskWithNameAndType(TaskName name, TaskType type) { + //Constructor from repo + public Task(TaskId taskId, TaskName taskName, TaskType taskType, OriginalTaskUri taskUri, + TaskStatus taskStatus) { + this.taskId = taskId; + this.taskName = taskName; + this.taskType = taskType; + this.originalTaskUri = taskUri; + this.taskStatus = taskStatus; + this.inputData = null; + this.outputData = null; + } + + + public 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, null); } - protected static Task createTaskWithNameAndTypeAndOriginalTaskUri(TaskName name, TaskType type, + public static Task createTaskWithNameAndTypeAndOriginalTaskUri(TaskName name, TaskType type, OriginalTaskUri originalTaskUri) { return new Task(name, type, originalTaskUri); } + //This is for recreating a task from a repository. + public static Task withIdNameTypeOriginaluriStatus(TaskId taskId, TaskName taskName, + TaskType taskType, + OriginalTaskUri originalTaskUri, + TaskStatus taskStatus) { + return new Task(taskId, taskName, taskType, originalTaskUri, taskStatus); + } + + @Value public static class TaskId { String value; diff --git a/tapas-tasks/src/main/resources/application.properties b/tapas-tasks/src/main/resources/application.properties index fe25873..badc0f8 100644 --- a/tapas-tasks/src/main/resources/application.properties +++ b/tapas-tasks/src/main/resources/application.properties @@ -1,2 +1,7 @@ server.port=8081 +//spring.data.mongodb.uri=mongodb://127.0.0.1:27017 +spring.data.mongodb.uri=mongodb://root:8nP7s0a@mongodb:27017/ +spring.data.mongodb.database=tapas-tasks baseuri=https://tapas-tasks.86-119-34-23.nip.io/ + + From b4efa1ee545b2d0b365a9e5a41a45ba7248c9132 Mon Sep 17 00:00:00 2001 From: Ronny Seiger Date: Thu, 11 Nov 2021 11:47:28 +0100 Subject: [PATCH 3/9] Minor change in dbadmin URL --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index e69f3c1..84b12fe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -94,7 +94,7 @@ services: ME_CONFIG_MONGODB_SERVER: mongodb labels: - "traefik.enable=true" - - "traefik.http.routers.dbadmin.rule=Host(`dbadmin1.${PUB_IP}.nip.io`)" + - "traefik.http.routers.dbadmin.rule=Host(`dbadmin.${PUB_IP}.nip.io`)" - "traefik.http.routers.dbadmin.service=dbadmin" - "traefik.http.services.dbadmin.loadbalancer.server.port=8081" - "traefik.http.routers.dbadmin.tls=true" From 3f4f2f4a1bcd8974fdd59ad8059e4ae788634af9 Mon Sep 17 00:00:00 2001 From: Andrei Ciortea Date: Thu, 11 Nov 2021 17:16:09 +0100 Subject: [PATCH 4/9] Update BROKERS.md --- tapas-auction-house/BROKERS.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tapas-auction-house/BROKERS.md b/tapas-auction-house/BROKERS.md index 7e0d37a..80eed02 100644 --- a/tapas-auction-house/BROKERS.md +++ b/tapas-auction-house/BROKERS.md @@ -14,10 +14,8 @@ Running this WebSub Hub implementation requires Docker, Node.js, and npm: ### How to run ```shell -git clone https://github.com/hemerajs/websub-hub.git -cd websub-hub -docker run -d -p 27017:27017 -p 28017:28017 -e AUTH=no tutum/mongodb -npm i -g websub-hub-cli +docker run -d -p 27017:27017 -p 28017:28017 -e AUTH=no mongo:latest +npm i -g websub-hub websub-hub -l info -m mongodb://localhost:27017/hub ``` From 343d33270a9b316667e54213fe1f4c4db8d6cf70 Mon Sep 17 00:00:00 2001 From: ronsei Date: Fri, 12 Nov 2021 10:21:05 +0100 Subject: [PATCH 5/9] Extending the README.md for tapas-tasks with details on setting up MongoDB as DB for repositories. --- tapas-tasks/README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tapas-tasks/README.md b/tapas-tasks/README.md index 90016c3..55f120f 100644 --- a/tapas-tasks/README.md +++ b/tapas-tasks/README.md @@ -167,3 +167,25 @@ Date: Sun, 17 Oct 2021 21:32:25 GMT Internally, this request is mapped to a [TaskExecutedEvent](src/main/java/ch/unisg/tapastasks/tasks/application/port/in/TaskExecutedEvent.java). The HTTP response returns a `200 OK` status code together with the updated representation of the task. + +## Working with MongoDB +The provided TAPAS Tasks service is connected to a MongoDB as a repository for persisting data. + +Here are some pointers to start integrating the MongoDB with the other microservices: +* [application.properties](src/main/resources/application.properties) defines the + * URI of the DB server that Spring will connect to (`mongodb`service running in Docker container). Username and password for the server can be found in [docker-compose.yml](../docker-compose.yml). + * Name of the database for the microservice (`tapas-tasks`) +* [docker-compose.yml](../docker-compose.yml) defines + * in lines 74-82: the configuration of the mongodb service based on the mongodb container including the root username and password (once deployed this cannot be changed anymore!) + * in lines 84-102: the configuration of a web application called `mongo-express` to manage the MongoDB server. The web app can be reached via the URI: [http://dbadmin.${PUB_IP}.nip.io]([http://dbadmin.${PUB_IP}.nip.io]). Login credentials for mongo-express can be found in lines 89 and 90. + * in lines 104-105: the volume to be used by the mongodb service for writing and storing data (do not forget!). +* The [pom.xml](./pom.xml) needs to have `spring-boot-starter-data-mongodb` and `spring-data-mongodb` as new dependencies. +* The [TapasTasksApplication.java](/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java) specifies in line 9 the location of the MongoRepository classes for the microservice. +* The [persistence.mongodb](/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb) package has the relevant classes to work with MongoDB: + * The [MongoTaskDocument.java](/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/MongoTaskDocument.java) class defines the attributes of a Document for storing a task in the collection `tasks`. + * The [TaskRepository.java](/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskRepository.java) class specifies the MongoRepository. + * The [TaskPersistenceAdapter.java](/src/main/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapter.java) implements the two ports to add a new task ([AddTaskPort](/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/AddTaskPort.java)) and retrieve a task ([LoadTaskPort](/src/main/java/ch/unisg/tapastasks/tasks/application/port/out/LoadTaskPort.java)). These ports are used in the classes [AddNewTaskToTaskListService.java](/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java) and [RetrieveTaskFromTaskListService.java](/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java). + +#### General hints: +* To not overload the VMs we recommend to use only one MongoDB server that all microservices connect to. Per microservice you could use one database or one collection (discuss in your ADRs!). To use more than one MongoDB server you have to extend the [docker-compose.yml](../docker-compose.yml) file by basically replicating lines 74-105 and changing the names of the services and volumes to be unique (ask your tutors!). +* For local testing you have to install the MongoDB server locally on your computers and change the `spring.data.mongodb.uri` String in [application.properties](./src/main/resources/application.properties). From df53236853a47969c82ef6ce312968841bd2ff87 Mon Sep 17 00:00:00 2001 From: Peter Guhl Date: Tue, 16 Nov 2021 16:12:15 +0100 Subject: [PATCH 6/9] Compiling and running tapas on a local docker installation (#10) * First version building tasks and mongodb * Creating container for local tests * Initial version * Added mongodb to correct network * Added auction house * Acknowledgments, Dockerfile for app * Added app to build * Dockerfile for app * Mapped app port to localhost:8080 Co-authored-by: Ubuntu --- app/Dockerfile | 13 ++++++++ docker-compose-local.yml | 60 ++++++++++++++++++++++++++++++++++ tapas-auction-house/Dockerfile | 13 ++++++++ tapas-tasks/Dockerfile | 13 ++++++++ 4 files changed, 99 insertions(+) create mode 100644 app/Dockerfile create mode 100644 docker-compose-local.yml create mode 100644 tapas-auction-house/Dockerfile create mode 100644 tapas-tasks/Dockerfile diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 0000000..429a177 --- /dev/null +++ b/app/Dockerfile @@ -0,0 +1,13 @@ +# Dockerfile/Docker-Compose file based on an initial version authored by Alexander Lontke (ASSE, Fall Semester 2021) + +FROM maven as build + +COPY . /app + +RUN mvn -f app/pom.xml --batch-mode --update-snapshots verify + +FROM openjdk + +COPY --from=build /app/target/app-0.1.0.jar ./app-0.1.0.jar + +CMD java -jar app-0.1.0.jar diff --git a/docker-compose-local.yml b/docker-compose-local.yml new file mode 100644 index 0000000..1ddfc24 --- /dev/null +++ b/docker-compose-local.yml @@ -0,0 +1,60 @@ +# Dockerfile/Docker-Compose file based on an initial version authored by Alexander Lontke (ASSE, Fall Semester 2021) + +version: "3.7" + +services: + app: + build: + context: ./app + dockerfile: Dockerfile + # Use environment variables instead of application.properties + environment: + - KEY=VALUE + ports: #Just needed when testing from outside the docker network + - "8080:8080" + networks: + - tapas-network + + tapas-tasks: + build: + context: ./tapas-tasks + dockerfile: Dockerfile + # Use environment variables instead of application.properties + environment: + - KEY=VALUE + ports: #Just needed when testing from outside + - "8081:8081" + networks: + - tapas-network + + tapas-auction-house: + build: + context: ./tapas-auction-house + dockerfile: Dockerfile + # Use environment variables instead of application.properties + environment: + - KEY=VALUE + ports: #Just needed when testing from outside + - "8082:8082" + networks: + - tapas-network + + mongodb: + image: mongo + container_name: mongodb + restart: unless-stopped + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: 8nP7s0a # Can not be changed again later on + volumes: + - database:/data/db + networks: + - tapas-network + +#Volume for mongodb. One per server. +volumes: + database: + +networks: + tapas-network: + driver: bridge diff --git a/tapas-auction-house/Dockerfile b/tapas-auction-house/Dockerfile new file mode 100644 index 0000000..fcc6d83 --- /dev/null +++ b/tapas-auction-house/Dockerfile @@ -0,0 +1,13 @@ +# Dockerfile/Docker-Compose file based on an initial version authored by Alexander Lontke (ASSE, Fall Semester 2021) + +FROM maven as build + +COPY . /app + +RUN mvn -f app/pom.xml --batch-mode --update-snapshots verify + +FROM openjdk + +COPY --from=build /app/target/tapas-auction-house-0.0.1-SNAPSHOT.jar ./tapas-auction-house-0.0.1-SNAPSHOT.jar + +CMD java -jar tapas-auction-house-0.0.1-SNAPSHOT.jar diff --git a/tapas-tasks/Dockerfile b/tapas-tasks/Dockerfile new file mode 100644 index 0000000..b05be85 --- /dev/null +++ b/tapas-tasks/Dockerfile @@ -0,0 +1,13 @@ +# Dockerfile/Docker-Compose file based on an initial version authored by Alexander Lontke (ASSE, Fall Semester 2021) + +FROM maven as build + +COPY . /app + +RUN mvn -f app/pom.xml --batch-mode --update-snapshots verify + +FROM openjdk + +COPY --from=build /app/target/tapas-tasks-0.0.1-SNAPSHOT.jar ./tapas-tasks-0.0.1-SNAPSHOT.jar + +CMD java -jar tapas-tasks-0.0.1-SNAPSHOT.jar From 292d30d1bdd4b1981f7e98333c9096cb80eab3aa Mon Sep 17 00:00:00 2001 From: Andrei Ciortea Date: Tue, 16 Nov 2021 22:41:22 +0100 Subject: [PATCH 7/9] Add example for reading config properties in the main method for TapasAuctionHouse --- .../tapas/TapasAuctionHouseApplication.java | 17 ++++++++++++++--- .../src/main/resources/application.properties | 2 ++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java index 8fc22d0..e21e82d 100644 --- a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java +++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java @@ -9,6 +9,8 @@ import org.apache.logging.log4j.Logger; import org.eclipse.paho.client.mqttv3.MqttException; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; import java.net.URI; import java.util.List; @@ -23,14 +25,15 @@ public class TapasAuctionHouseApplication { public static String RESOURCE_DIRECTORY = "https://api.interactions.ics.unisg.ch/auction-houses/"; public static String MQTT_BROKER = "tcp://broker.hivemq.com:1883"; + private static ConfigurableEnvironment ENVIRONMENT; + public static void main(String[] args) { SpringApplication tapasAuctioneerApp = new SpringApplication(TapasAuctionHouseApplication.class); + ENVIRONMENT = tapasAuctioneerApp.run(args).getEnvironment(); // We will use these bootstrap methods in Week 6: // bootstrapMarketplaceWithWebSub(); // bootstrapMarketplaceWithMqtt(); - - tapasAuctioneerApp.run(args); } /** @@ -53,8 +56,16 @@ public class TapasAuctionHouseApplication { */ private static void bootstrapMarketplaceWithMqtt() { try { + String broker = ENVIRONMENT.getProperty("broker.mqtt"); + + if (broker == null) { + broker = MQTT_BROKER; + LOGGER.info("No MQTT broker was set in application.propreties, going with default: " + + MQTT_BROKER); + } + AuctionEventsMqttDispatcher dispatcher = new AuctionEventsMqttDispatcher(); - TapasMqttClient client = TapasMqttClient.getInstance(MQTT_BROKER, dispatcher); + TapasMqttClient client = TapasMqttClient.getInstance(broker, dispatcher); client.startReceivingMessages(); } catch (MqttException e) { LOGGER.error(e.getMessage(), e); diff --git a/tapas-auction-house/src/main/resources/application.properties b/tapas-auction-house/src/main/resources/application.properties index 2c92c87..f69e5dd 100644 --- a/tapas-auction-house/src/main/resources/application.properties +++ b/tapas-auction-house/src/main/resources/application.properties @@ -1,5 +1,7 @@ server.port=8082 +broker.mqtt=tcp://broker.hivemq.com + websub.hub=https://websub.appspot.com/ websub.hub.publish=https://websub.appspot.com/ From c126c349720617b9d24f9c4487b181fefea7a3e8 Mon Sep 17 00:00:00 2001 From: ronsei Date: Thu, 18 Nov 2021 16:02:28 +0100 Subject: [PATCH 8/9] First set tests for Assignment 8 --- tapas-tasks/pom.xml | 19 +++++ .../service/AddNewTaskToTaskListService.java | 3 + .../RetrieveTaskFromTaskListService.java | 2 + .../src/main/resources/application.properties | 35 ++++++++- .../AddNewTaskToTaskListSystemTest.java | 76 ++++++++++++++++++ .../TapasTasksApplicationTests.java | 7 +- ...AddNewTaskToTaskListWebControllerTest.java | 68 ++++++++++++++++ .../mongodb/TaskPersistenceAdapterTest.java | 77 +++++++++++++++++++ .../AddNewTaskToTaskListServiceTest.java | 63 +++++++++++++++ .../tapastasks/tasks/domain/TaskListTest.java | 49 ++++++++++++ .../resources/application-test.properties | 2 + 11 files changed, 398 insertions(+), 3 deletions(-) create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/AddNewTaskToTaskListSystemTest.java create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebControllerTest.java create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapterTest.java create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListServiceTest.java create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/domain/TaskListTest.java create mode 100644 tapas-tasks/src/test/resources/application-test.properties diff --git a/tapas-tasks/pom.xml b/tapas-tasks/pom.xml index 7dcf6ae..6dcc158 100644 --- a/tapas-tasks/pom.xml +++ b/tapas-tasks/pom.xml @@ -37,6 +37,16 @@ spring-boot-starter-data-mongodb + + org.springframework.boot + spring-boot-starter-actuator + + + de.codecentric + chaos-monkey-spring-boot + 2.5.4 + + org.projectlombok lombok @@ -47,6 +57,11 @@ spring-boot-starter-test test + + org.mockito + mockito-core + 2.21.0 + org.springframework.boot @@ -74,6 +89,10 @@ org.eclipse.paho.client.mqttv3 1.2.0 + + org.springframework.boot + spring-boot-test + diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java index d78de83..9776064 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListService.java @@ -11,11 +11,14 @@ import ch.unisg.tapastasks.tasks.domain.NewTaskAddedEvent; import ch.unisg.tapastasks.tasks.domain.TaskList; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + import javax.transaction.Transactional; @RequiredArgsConstructor @Component @Transactional +@Service("AddNewTaskToTaskList") public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase { private final NewTaskAddedEventPort newTaskAddedEventPort; diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java index 5e5ec29..39026f5 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/tasks/application/service/RetrieveTaskFromTaskListService.java @@ -7,6 +7,7 @@ import ch.unisg.tapastasks.tasks.domain.Task; import ch.unisg.tapastasks.tasks.domain.TaskList; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import javax.transaction.Transactional; import java.util.Optional; @@ -14,6 +15,7 @@ import java.util.Optional; @RequiredArgsConstructor @Component @Transactional +@Service("RetrieveTaskFromTaskList") public class RetrieveTaskFromTaskListService implements RetrieveTaskFromTaskListUseCase { private final LoadTaskPort loadTaskFromRepositoryPort; diff --git a/tapas-tasks/src/main/resources/application.properties b/tapas-tasks/src/main/resources/application.properties index badc0f8..7588f89 100644 --- a/tapas-tasks/src/main/resources/application.properties +++ b/tapas-tasks/src/main/resources/application.properties @@ -1,7 +1,38 @@ server.port=8081 -//spring.data.mongodb.uri=mongodb://127.0.0.1:27017 -spring.data.mongodb.uri=mongodb://root:8nP7s0a@mongodb:27017/ +spring.data.mongodb.uri=mongodb://127.0.0.1:27017 +#spring.data.mongodb.uri=mongodb://root:8nP7s0a@mongodb:27017/ spring.data.mongodb.database=tapas-tasks baseuri=https://tapas-tasks.86-119-34-23.nip.io/ +spring.profiles.active=chaos-monkey +chaos.monkey.enabled=true +management.endpoint.chaosmonkey.enabled=true +management.endpoint.chaosmonkeyjmx.enabled=true +# include specific endpoints +management.endpoints.web.exposure.include=health,info,chaosmonkey +chaos.monkey.watcher.controller=true +chaos.monkey.watcher.restController=true +chaos.monkey.watcher.service=true +chaos.monkey.watcher.repository=true +chaos.monkey.watcher.component=true +#Chaos Monkey configs taken from here: https://www.baeldung.com/spring-boot-chaos-monkey + +#Latency Assault +#chaos.monkey.assaults.latencyActive=true +#chaos.monkey.assaults.latencyRangeStart=3000 +#chaos.monkey.assaults.latencyRangeEnd=15000 + +#Exception Assault +#chaos.monkey.assaults.latencyActive=false +#chaos.monkey.assaults.exceptionsActive=true +#chaos.monkey.assaults.killApplicationActive=false + +#AppKiller Assault +#chaos.monkey.assaults.latencyActive=false +#chaos.monkey.assaults.exceptionsActive=false +#chaos.monkey.assaults.killApplicationActive=true + +#Chaos Monkey assaults via REST to endpoint /actuator/chaosmonkey/assaults/ +#https://softwarehut.com/blog/tech/chaos-monkey +#https://codecentric.github.io/chaos-monkey-spring-boot/latest/ diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/AddNewTaskToTaskListSystemTest.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/AddNewTaskToTaskListSystemTest.java new file mode 100644 index 0000000..e17c2e2 --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/AddNewTaskToTaskListSystemTest.java @@ -0,0 +1,76 @@ +package ch.unisg.tapastasks; + +import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonRepresentation; +import ch.unisg.tapastasks.tasks.application.port.out.AddTaskPort; +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.*; + +import static org.assertj.core.api.BDDAssertions.*; + + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class AddNewTaskToTaskListSystemTest { + + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + private AddTaskPort addTaskPort; + + @Test + void addNewTaskToTaskList() throws JSONException { + + Task.TaskName taskName = new Task.TaskName("system-integration-test-task"); + Task.TaskType taskType = new Task.TaskType("system-integration-test-type"); + Task.OriginalTaskUri originalTaskUri = new Task.OriginalTaskUri("example.org"); + + ResponseEntity response = whenAddNewTaskToEmptyList(taskName, taskType, originalTaskUri); + + JSONObject responseJson = new JSONObject(response.getBody().toString()); + String respTaskId = responseJson.getString("taskId"); + String respTaskName = responseJson.getString("taskName"); + String respTaskType = responseJson.getString("taskType"); + + then(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); + then(respTaskId).isNotEmpty(); + then(respTaskName).isEqualTo(taskName.getValue()); + then(respTaskType).isEqualTo(taskType.getValue()); + then(TaskList.getTapasTaskList().getListOfTasks().getValue()).hasSize(1); + + } + + private ResponseEntity whenAddNewTaskToEmptyList( + Task.TaskName taskName, + Task.TaskType taskType, + Task.OriginalTaskUri originalTaskUri) throws JSONException { + + TaskList.getTapasTaskList().getListOfTasks().getValue().clear(); + + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Type", TaskJsonRepresentation.MEDIA_TYPE); + + String jsonPayLoad = new JSONObject() + .put("taskName", taskName.getValue() ) + .put("taskType", taskType.getValue()) + .put("originalTaskUri",originalTaskUri.getValue()) + .toString(); + + HttpEntity request = new HttpEntity<>(jsonPayLoad,headers); + + return restTemplate.exchange( + "/tasks/", + HttpMethod.POST, + request, + Object.class + ); + + } + +} diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/TapasTasksApplicationTests.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/TapasTasksApplicationTests.java index b96cb8b..a343151 100644 --- a/tapas-tasks/src/test/java/ch/unisg/tapastasks/TapasTasksApplicationTests.java +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/TapasTasksApplicationTests.java @@ -1,13 +1,18 @@ package ch.unisg.tapastasks; +import ch.unisg.tapastasks.tasks.application.port.out.AddTaskPort; +import ch.unisg.tapastasks.tasks.application.port.out.NewTaskAddedEventPort; +import ch.unisg.tapastasks.tasks.application.port.out.TaskListLock; +import ch.unisg.tapastasks.tasks.application.service.AddNewTaskToTaskListService; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class TapasTasksApplicationTests { - @Test + @Test void contextLoads() { + } } diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebControllerTest.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebControllerTest.java new file mode 100644 index 0000000..d397908 --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/adapter/in/web/AddNewTaskToTaskListWebControllerTest.java @@ -0,0 +1,68 @@ +package ch.unisg.tapastasks.tasks.adapter.in.web; + +import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonRepresentation; +import ch.unisg.tapastasks.tasks.adapter.in.web.AddNewTaskToTaskListWebController; +import ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb.TaskRepository; +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.json.JSONObject; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Optional; + +import static org.mockito.BDDMockito.eq; +import static org.mockito.BDDMockito.then; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@WebMvcTest(controllers = AddNewTaskToTaskListWebController.class) +public class AddNewTaskToTaskListWebControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private AddNewTaskToTaskListUseCase addNewTaskToTaskListUseCase; + + @MockBean + TaskRepository taskRepository; + + @Disabled + @Test + void testAddNewTaskToTaskList() throws Exception { + + String taskName = "test-request"; + String taskType = "test-request-type"; + String originalTaskUri = "example.org"; + + String jsonPayLoad = new JSONObject() + .put("taskName", taskName ) + .put("taskType", taskType) + .put("originalTaskUri",originalTaskUri) + .toString(); + + //This raises a NullPointerException since it tries to build the HTTP response with attributes from + //the domain object (created task), which is mocked --> we need System Tests here! + //See the buckpal example from the lecture for a working integration test for testing the web controller + mockMvc.perform(post("/tasks/") + .contentType(TaskJsonRepresentation.MEDIA_TYPE) + .content(jsonPayLoad)) + .andExpect(status().isCreated()); + + then(addNewTaskToTaskListUseCase).should() + .addNewTaskToTaskList(eq(new AddNewTaskToTaskListCommand( + new Task.TaskName(taskName), new Task.TaskType(taskType), + Optional.of(new Task.OriginalTaskUri(originalTaskUri)) + ))); + + } + + +} diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapterTest.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapterTest.java new file mode 100644 index 0000000..ef1fa99 --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/adapter/out/persistence/mongodb/TaskPersistenceAdapterTest.java @@ -0,0 +1,77 @@ +package ch.unisg.tapastasks.tasks.adapter.out.persistence.mongodb; + +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.data.mongo.AutoConfigureDataMongo; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@AutoConfigureDataMongo +@Import({TaskPersistenceAdapter.class, TaskMapper.class}) +public class TaskPersistenceAdapterTest { + + @Autowired + private TaskRepository taskRepository; + + @Autowired + private TaskPersistenceAdapter adapterUnderTest; + + @Test + void addsNewTask() { + + String testTaskId = UUID.randomUUID().toString(); + String testTaskName = "adds-persistence-task-name"; + String testTaskType = "adds-persistence-task-type"; + String testTaskOuri = "adds-persistence-test-task-ouri"; + String testTaskStatus = Task.Status.OPEN.toString(); + String testTaskListName = "tapas-tasks-tutors"; + + + Task testTask = new Task( + new Task.TaskId(testTaskId), + new Task.TaskName(testTaskName), + new Task.TaskType(testTaskType), + new Task.OriginalTaskUri(testTaskOuri), + new Task.TaskStatus(Task.Status.valueOf(testTaskStatus)) + ); + adapterUnderTest.addTask(testTask); + + MongoTaskDocument retrievedDoc = taskRepository.findByTaskId(testTaskId,testTaskListName); + + assertThat(retrievedDoc.taskId).isEqualTo(testTaskId); + assertThat(retrievedDoc.taskName).isEqualTo(testTaskName); + assertThat(retrievedDoc.taskListName).isEqualTo(testTaskListName); + + } + + @Test + void retrievesTask() { + + String testTaskId = UUID.randomUUID().toString(); + String testTaskName = "reads-persistence-task-name"; + String testTaskType = "reads-persistence-task-type"; + String testTaskOuri = "reads-persistence-test-task-ouri"; + String testTaskStatus = Task.Status.OPEN.toString(); + String testTaskListName = "tapas-tasks-tutors"; + + MongoTaskDocument mongoTask = new MongoTaskDocument(testTaskId, testTaskName, testTaskType, testTaskOuri, + testTaskStatus, testTaskListName); + taskRepository.insert(mongoTask); + + Task retrievedTask = adapterUnderTest.loadTask(new Task.TaskId(testTaskId), + new TaskList.TaskListName(testTaskListName)); + + assertThat(retrievedTask.getTaskName().getValue()).isEqualTo(testTaskName); + assertThat(retrievedTask.getTaskId().getValue()).isEqualTo(testTaskId); + assertThat(retrievedTask.getTaskStatus().getValue()).isEqualTo(Task.Status.valueOf(testTaskStatus)); + + } + +} diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListServiceTest.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListServiceTest.java new file mode 100644 index 0000000..10d21fd --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/application/service/AddNewTaskToTaskListServiceTest.java @@ -0,0 +1,63 @@ +package ch.unisg.tapastasks.tasks.application.service; + +import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListCommand; +import ch.unisg.tapastasks.tasks.application.port.out.AddTaskPort; +import ch.unisg.tapastasks.tasks.application.port.out.NewTaskAddedEventPort; +import ch.unisg.tapastasks.tasks.application.port.out.TaskListLock; +import ch.unisg.tapastasks.tasks.domain.NewTaskAddedEvent; +import ch.unisg.tapastasks.tasks.domain.Task; +import ch.unisg.tapastasks.tasks.domain.TaskList; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Optional; + +import static org.mockito.BDDMockito.*; +import static org.assertj.core.api.Assertions.*; + + +public class AddNewTaskToTaskListServiceTest { + + private final AddTaskPort addTaskPort = Mockito.mock(AddTaskPort.class); + private final TaskListLock taskListLock = Mockito.mock(TaskListLock.class); + private final NewTaskAddedEventPort newTaskAddedEventPort = Mockito.mock(NewTaskAddedEventPort.class); + private final AddNewTaskToTaskListService addNewTaskToTaskListService = new AddNewTaskToTaskListService( + newTaskAddedEventPort, addTaskPort, taskListLock); + + @Test + void addingSucceeds() { + + Task newTask = givenATaskWithNameAndTypeAndURI(new Task.TaskName("test-task"), + new Task.TaskType("test-type"), Optional.of(new Task.OriginalTaskUri("example.org"))); + + TaskList taskList = givenAnEmptyTaskList(TaskList.getTapasTaskList()); + + AddNewTaskToTaskListCommand addNewTaskToTaskListCommand = new AddNewTaskToTaskListCommand(newTask.getTaskName(), + newTask.getTaskType(), Optional.ofNullable(newTask.getOriginalTaskUri())); + + Task addedTask = addNewTaskToTaskListService.addNewTaskToTaskList(addNewTaskToTaskListCommand); + + assertThat(addedTask).isNotNull(); + assertThat(taskList.getListOfTasks().getValue()).hasSize(1); + + then(taskListLock).should().lockTaskList(eq(TaskList.getTapasTaskList().getTaskListName())); + then(newTaskAddedEventPort).should(times(1)) + .publishNewTaskAddedEvent(any(NewTaskAddedEvent.class)); + + } + + private TaskList givenAnEmptyTaskList(TaskList taskList) { + taskList.getListOfTasks().getValue().clear(); + return taskList; + } + + private Task givenATaskWithNameAndTypeAndURI(Task.TaskName taskName, Task.TaskType taskType, + Optional originalTaskUri) { + Task task = Mockito.mock(Task.class); + given(task.getTaskName()).willReturn(taskName); + given(task.getTaskType()).willReturn(taskType); + given(task.getOriginalTaskUri()).willReturn(originalTaskUri.get()); + return task; + } + +} diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/domain/TaskListTest.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/domain/TaskListTest.java new file mode 100644 index 0000000..05dc664 --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/tasks/domain/TaskListTest.java @@ -0,0 +1,49 @@ +package ch.unisg.tapastasks.tasks.domain; + +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; + + +public class TaskListTest { + + @Test + void addNewTaskToTaskListSuccess() { + TaskList taskList = TaskList.getTapasTaskList(); + taskList.getListOfTasks().getValue().clear(); + Task newTask = taskList.addNewTaskWithNameAndType(new Task.TaskName("My-Test-Task"), + new Task.TaskType("My-Test-Type")); + + assertThat(newTask.getTaskName().getValue()).isEqualTo("My-Test-Task"); + assertThat(taskList.getListOfTasks().getValue()).hasSize(1); + assertThat(taskList.getListOfTasks().getValue().get(0)).isEqualTo(newTask); + + } + + @Test + void retrieveTaskSuccess() { + TaskList taskList = TaskList.getTapasTaskList(); + Task newTask = taskList.addNewTaskWithNameAndType(new Task.TaskName("My-Test-Task2"), + new Task.TaskType("My-Test-Type2")); + + Task retrievedTask = taskList.retrieveTaskById(newTask.getTaskId()).get(); + + assertThat(retrievedTask).isEqualTo(newTask); + + } + + @Test + void retrieveTaskFailure() { + TaskList taskList = TaskList.getTapasTaskList(); + Task newTask = taskList.addNewTaskWithNameAndType(new Task.TaskName("My-Test-Task3"), + new Task.TaskType("My-Test-Type3")); + + Task.TaskId fakeId = new Task.TaskId("fake-id"); + + Optional retrievedTask = taskList.retrieveTaskById(fakeId); + + assertThat(retrievedTask.isPresent()).isFalse(); + } +} diff --git a/tapas-tasks/src/test/resources/application-test.properties b/tapas-tasks/src/test/resources/application-test.properties new file mode 100644 index 0000000..e45b53d --- /dev/null +++ b/tapas-tasks/src/test/resources/application-test.properties @@ -0,0 +1,2 @@ +spring.data.mongodb.uri=mongodb://127.0.0.1:27017 +spring.data.mongodb.database=tapas-tasks From f6c0495c047023a010097ec6f0a6559526766c72 Mon Sep 17 00:00:00 2001 From: ronsei Date: Thu, 18 Nov 2021 18:13:49 +0100 Subject: [PATCH 9/9] ArchUnit test for Assignment 8 --- tapas-tasks/pom.xml | 6 ++ .../unisg/tapastasks/DependencyRuleTests.java | 24 ++++++ .../unisg/tapastasks/archunit/Adapters.java | 62 ++++++++++++++++ .../tapastasks/archunit/ApplicationLayer.java | 59 +++++++++++++++ .../archunit/ArchitectureElement.java | 74 +++++++++++++++++++ .../archunit/HexagonalArchitecture.java | 61 +++++++++++++++ 6 files changed, 286 insertions(+) create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/DependencyRuleTests.java create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/Adapters.java create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/ApplicationLayer.java create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/ArchitectureElement.java create mode 100644 tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/HexagonalArchitecture.java diff --git a/tapas-tasks/pom.xml b/tapas-tasks/pom.xml index 6dcc158..25ef1f2 100644 --- a/tapas-tasks/pom.xml +++ b/tapas-tasks/pom.xml @@ -62,6 +62,12 @@ mockito-core 2.21.0 + + com.tngtech.archunit + archunit-junit4 + 0.22.0 + test + org.springframework.boot diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/DependencyRuleTests.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/DependencyRuleTests.java new file mode 100644 index 0000000..0d26f37 --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/DependencyRuleTests.java @@ -0,0 +1,24 @@ +package ch.unisg.tapastasks; + +import ch.unisg.tapastasks.archunit.HexagonalArchitecture; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +class DependencyRuleTests { + + @Test + void testPackageDependencies() { + noClasses() + .that() + .resideInAPackage("ch.unisg.tapastasks.tasks.domain..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("ch.unisg.tapastasks.tasks.application..") + .check(new ClassFileImporter() + .importPackages("ch.unisg.tapastasks.tasks..")); + } + +} diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/Adapters.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/Adapters.java new file mode 100644 index 0000000..92a764b --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/Adapters.java @@ -0,0 +1,62 @@ +package ch.unisg.tapastasks.archunit; + +import com.tngtech.archunit.core.domain.JavaClasses; + +import java.util.ArrayList; +import java.util.List; + +public class Adapters extends ArchitectureElement { + + private final HexagonalArchitecture parentContext; + private List incomingAdapterPackages = new ArrayList<>(); + private List outgoingAdapterPackages = new ArrayList<>(); + + Adapters(HexagonalArchitecture parentContext, String basePackage) { + super(basePackage); + this.parentContext = parentContext; + } + + public Adapters outgoing(String packageName) { + this.incomingAdapterPackages.add(fullQualifiedPackage(packageName)); + return this; + } + + public Adapters incoming(String packageName) { + this.outgoingAdapterPackages.add(fullQualifiedPackage(packageName)); + return this; + } + + List allAdapterPackages() { + List allAdapters = new ArrayList<>(); + allAdapters.addAll(incomingAdapterPackages); + allAdapters.addAll(outgoingAdapterPackages); + return allAdapters; + } + + public HexagonalArchitecture and() { + return parentContext; + } + + String getBasePackage() { + return basePackage; + } + + void dontDependOnEachOther(JavaClasses classes) { + List allAdapters = allAdapterPackages(); + for (String adapter1 : allAdapters) { + for (String adapter2 : allAdapters) { + if (!adapter1.equals(adapter2)) { + denyDependency(adapter1, adapter2, classes); + } + } + } + } + + void doesNotDependOn(String packageName, JavaClasses classes) { + denyDependency(this.basePackage, packageName, classes); + } + + void doesNotContainEmptyPackages() { + denyEmptyPackages(allAdapterPackages()); + } +} diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/ApplicationLayer.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/ApplicationLayer.java new file mode 100644 index 0000000..e8e00a5 --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/ApplicationLayer.java @@ -0,0 +1,59 @@ +package ch.unisg.tapastasks.archunit; + +import com.tngtech.archunit.core.domain.JavaClasses; + +import java.util.ArrayList; +import java.util.List; + +public class ApplicationLayer extends ArchitectureElement { + + private final HexagonalArchitecture parentContext; + private List incomingPortsPackages = new ArrayList<>(); + private List outgoingPortsPackages = new ArrayList<>(); + private List servicePackages = new ArrayList<>(); + + public ApplicationLayer(String basePackage, HexagonalArchitecture parentContext) { + super(basePackage); + this.parentContext = parentContext; + } + + public ApplicationLayer incomingPorts(String packageName) { + this.incomingPortsPackages.add(fullQualifiedPackage(packageName)); + return this; + } + + public ApplicationLayer outgoingPorts(String packageName) { + this.outgoingPortsPackages.add(fullQualifiedPackage(packageName)); + return this; + } + + public ApplicationLayer services(String packageName) { + this.servicePackages.add(fullQualifiedPackage(packageName)); + return this; + } + + public HexagonalArchitecture and() { + return parentContext; + } + + public void doesNotDependOn(String packageName, JavaClasses classes) { + denyDependency(this.basePackage, packageName, classes); + } + + public void incomingAndOutgoingPortsDoNotDependOnEachOther(JavaClasses classes) { + denyAnyDependency(this.incomingPortsPackages, this.outgoingPortsPackages, classes); + denyAnyDependency(this.outgoingPortsPackages, this.incomingPortsPackages, classes); + } + + private List allPackages() { + List allPackages = new ArrayList<>(); + allPackages.addAll(incomingPortsPackages); + allPackages.addAll(outgoingPortsPackages); + allPackages.addAll(servicePackages); + return allPackages; + } + + void doesNotContainEmptyPackages() { + denyEmptyPackages(allPackages()); + } +} diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/ArchitectureElement.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/ArchitectureElement.java new file mode 100644 index 0000000..465f441 --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/ArchitectureElement.java @@ -0,0 +1,74 @@ +package ch.unisg.tapastasks.archunit; + +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.importer.ClassFileImporter; + +import java.util.List; + +import static com.tngtech.archunit.base.DescribedPredicate.*; +import static com.tngtech.archunit.base.DescribedPredicate.greaterThanOrEqualTo; +import static com.tngtech.archunit.lang.conditions.ArchConditions.*; +import static com.tngtech.archunit.lang.conditions.ArchConditions.containNumberOfElements; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +abstract class ArchitectureElement { + + final String basePackage; + + public ArchitectureElement(String basePackage) { + this.basePackage = basePackage; + } + + String fullQualifiedPackage(String relativePackage) { + return this.basePackage + "." + relativePackage; + } + + static void denyDependency(String fromPackageName, String toPackageName, JavaClasses classes) { + noClasses() + .that() + .resideInAPackage("ch.unisg.tapastasks.tasks.domain..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("ch.unisg.tapastasks.tasks.application..") + .check(classes); + } + + static void denyAnyDependency( + List fromPackages, List toPackages, JavaClasses classes) { + for (String fromPackage : fromPackages) { + for (String toPackage : toPackages) { + noClasses() + .that() + .resideInAPackage(matchAllClassesInPackage(fromPackage)) + .should() + .dependOnClassesThat() + .resideInAnyPackage(matchAllClassesInPackage(toPackage)) + .check(classes); + } + } + } + + static String matchAllClassesInPackage(String packageName) { + return packageName + ".."; + } + + void denyEmptyPackage(String packageName) { + classes() + .that() + .resideInAPackage(matchAllClassesInPackage(packageName)) + .should(containNumberOfElements(greaterThanOrEqualTo(1))) + .check(classesInPackage(packageName)); + } + + private JavaClasses classesInPackage(String packageName) { + return new ClassFileImporter().importPackages(packageName); + } + + void denyEmptyPackages(List packages) { + for (String packageName : packages) { + denyEmptyPackage(packageName); + } + } +} diff --git a/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/HexagonalArchitecture.java b/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/HexagonalArchitecture.java new file mode 100644 index 0000000..1445968 --- /dev/null +++ b/tapas-tasks/src/test/java/ch/unisg/tapastasks/archunit/HexagonalArchitecture.java @@ -0,0 +1,61 @@ +package ch.unisg.tapastasks.archunit; + +import com.tngtech.archunit.core.domain.JavaClasses; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class HexagonalArchitecture extends ArchitectureElement { + + private Adapters adapters; + private ApplicationLayer applicationLayer; + private String configurationPackage; + private List domainPackages = new ArrayList<>(); + + public static HexagonalArchitecture boundedContext(String basePackage) { + return new HexagonalArchitecture(basePackage); + } + + public HexagonalArchitecture(String basePackage) { + super(basePackage); + } + + public Adapters withAdaptersLayer(String adaptersPackage) { + this.adapters = new Adapters(this, fullQualifiedPackage(adaptersPackage)); + return this.adapters; + } + + public HexagonalArchitecture withDomainLayer(String domainPackage) { + this.domainPackages.add(fullQualifiedPackage(domainPackage)); + return this; + } + + public ApplicationLayer withApplicationLayer(String applicationPackage) { + this.applicationLayer = new ApplicationLayer(fullQualifiedPackage(applicationPackage), this); + return this.applicationLayer; + } + + public HexagonalArchitecture withConfiguration(String packageName) { + this.configurationPackage = fullQualifiedPackage(packageName); + return this; + } + + private void domainDoesNotDependOnOtherPackages(JavaClasses classes) { + denyAnyDependency( + this.domainPackages, Collections.singletonList(adapters.basePackage), classes); + denyAnyDependency( + this.domainPackages, Collections.singletonList(applicationLayer.basePackage), classes); + } + + public void check(JavaClasses classes) { + this.adapters.doesNotContainEmptyPackages(); + this.adapters.dontDependOnEachOther(classes); + this.adapters.doesNotDependOn(this.configurationPackage, classes); + this.applicationLayer.doesNotContainEmptyPackages(); + this.applicationLayer.doesNotDependOn(this.adapters.getBasePackage(), classes); + this.applicationLayer.doesNotDependOn(this.configurationPackage, classes); + this.applicationLayer.incomingAndOutgoingPortsDoNotDependOnEachOther(classes); + this.domainDoesNotDependOnOtherPackages(classes); + } +}