|
||
---|---|---|
.. | ||
.mvn/wrapper | ||
src | ||
.editorconfig | ||
.gitignore | ||
Dockerfile | ||
mvnw | ||
mvnw.cmd | ||
pom.xml | ||
README.md |
tapas-tasks
Micro-service for Managing Tasks in a Task List implemented following Hexagonal Architecture.
Based on examples from book "Get Your Hands Dirty on Clean Architecture" by Tom Hombergs
Technologies: Java, Spring Boot, Maven
Note: this repository contains an EditorConfig file (.editorconfig
)
with default editor settings. EditorConfig is supported out-of-the-box by the IntelliJ IDE. To help maintain
consistent code styles, we recommend to reuse this editor configuration file in all your services.
HTTP API Overview
The code we provide includes a minimalistic uniform HTTP API for (i) creating a new task, (ii) retrieving a representation of the current state of a task, and (iii) patching the representation of a task, which is mapped to a domain/integration event.
The representations exchanged with the API use two media types:
- a JSON-based format for task with the media type
application/task+json
; this media type is defined in the context of our project, but could be registered with IANA to promote interoperability (see TaskJsonRepresentation for more details) - the JSON Patch format with the registered media type
application/json-patch+json
, which is also a JSON-based format (see sample HTTP requests below).
For further developing and working with your HTTP API, we recommend to use Postman.
Creating a new task
A new task is created via an HTTP POST
request to the /tasks/
endpoint. The body of the request
must include a representation of the task to be created using the content type application/task+json
defined in the context of this project. A valid representation must include at least two required fields
(see TaskJsonRepresentation
for more details):
taskName
: a string that represents the name of the task to be createdtaskType
: a string that represents the type of the task to be created
A sample HTTP request with curl
:
curl -i --location --request POST 'http://localhost:8081/tasks/' \
--header 'Content-Type: application/task+json' \
--data-raw '{
"taskName" : "task1",
"taskType" : "computation",
"originalTaskUri" : "http://example.org",
"inputData" : "1+1"
}'
HTTP/1.1 201
Location: http://localhost:8081/tasks/cef2fa9d-367b-4e7f-bf06-3b1fea35f354
Content-Type: application/task+json
Content-Length: 170
Date: Sun, 17 Oct 2021 21:03:34 GMT
{
"taskId":"cef2fa9d-367b-4e7f-bf06-3b1fea35f354",
"taskName":"task1",
"taskType":"computation",
"taskStatus":"OPEN",
"originalTaskUri":"http://example.org",
"inputData":"1+1"
}
If the task is created successfuly, a 201 Created
status code is returned together with a
representation of the created task. The response also includes a Location
header filed that points
to the URI of the newly created task.
Retrieving a task
The representation of a task is retrieved via an HTTP GET
request to the URI of task.
A sample HTTP request with curl
:
curl -i --location --request GET 'http://localhost:8081/tasks/cef2fa9d-367b-4e7f-bf06-3b1fea35f354'
HTTP/1.1 200
Content-Type: application/task+json
Content-Length: 170
Date: Sun, 17 Oct 2021 21:07:04 GMT
{
"taskId":"cef2fa9d-367b-4e7f-bf06-3b1fea35f354",
"taskName":"task1",
"taskType":"computation",
"taskStatus":"OPEN",
"originalTaskUri":"http://example.org",
"inputData":"1+1"
}
Patching a task
REST emphasizes the generality of interfaces to promote uniform interaction. For instance, we can use
the HTTP PATCH
method to implement fine-grained updates to the representational state of a task, which
may translate to various domain/integration events. However, to conform to the uniform interface
contraint in REST, any such updates have to rely on standard knowledge — and thus to hide away the
implementation details of our service.
In addition to the application/task+json
media type we defined for our uniform HTTP API, a standard
representation format we can use to specify fine-grained updates to the representation of tasks
is JSON Patch. In what follow, we provide a few examples of HTTP PATCH
requests.
For further details on the JSON Patch format, see also RFC 6902).
Changing the status of a task from OPEN to ASSIGNED
Sample HTTP request that assigns the previously created task to group tapas-group1
:
curl -i --location --request PATCH 'http://localhost:8081/tasks/cef2fa9d-367b-4e7f-bf06-3b1fea35f354' \
--header 'Content-Type: application/json-patch+json' \
--data-raw '[ {"op" : "replace", "path": "/taskStatus", "value" : "ASSIGNED" },
{"op" : "add", "path": "/serviceProvider", "value" : "tapas-group1" } ]'
HTTP/1.1 200
Content-Type: application/task+json
Content-Length: 207
Date: Sun, 17 Oct 2021 21:20:58 GMT
{
"taskId":"cef2fa9d-367b-4e7f-bf06-3b1fea35f354",
"taskName":"task1",
"taskType":"computation",
"taskStatus":"ASSIGNED",
"originalTaskUri":"http://example.org",
"serviceProvider":"tapas-group1",
"inputData":"1+1"
}
In this example, the requested patch includes two JSON Patch operations:
- an operation to
replace
thetaskStatus
already in the task's representation with the valueASSIGNED
- an operation to
add
to the task's representation aserviceProvider
with the valuetapas-group1
Internally, this request is mapped to a
TaskAssignedEvent.
The HTTP response returns a 200 OK
status code together with the updated representation of the task.
Changing the status of a task from to EXECUTED
Sample HTTP request that changes the status of the task to EXECUTED
and adds an output result:
curl -i --location --request PATCH 'http://localhost:8081/tasks/cef2fa9d-367b-4e7f-bf06-3b1fea35f354' \
--header 'Content-Type: application/json-patch+json' \
--data-raw '[ {"op" : "replace", "path": "/taskStatus", "value" : "EXECUTED" },
{"op" : "add", "path": "/outputData", "value" : "2" } ]'
HTTP/1.1 200
Content-Type: application/task+json
Content-Length: 224
Date: Sun, 17 Oct 2021 21:32:25 GMT
{
"taskId":"cef2fa9d-367b-4e7f-bf06-3b1fea35f354",
"taskName":"task1",
"taskType":"computation",
"taskStatus":"EXECUTED",
"originalTaskUri":"http://example.org",
"serviceProvider":"tapas-group1",
"inputData":"1+1",
"outputData":"2"
}
Internally, this request is mapped to a
TaskExecutedEvent.
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 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. - Name of the database for the microservice (
tapas-tasks
)
- URI of the DB server that Spring will connect to (
- 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. 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 needs to have
spring-boot-starter-data-mongodb
andspring-data-mongodb
as new dependencies. - The TapasTasksApplication.java specifies in line 9 the location of the MongoRepository classes for the microservice.
- The persistence.mongodb package has the relevant classes to work with MongoDB:
- The MongoTaskDocument.java class defines the attributes of a Document for storing a task in the collection
tasks
. - The TaskRepository.java class specifies the MongoRepository.
- The TaskPersistenceAdapter.java implements the two ports to add a new task (AddTaskPort) and retrieve a task (LoadTaskPort). These ports are used in the classes AddNewTaskToTaskListService.java and RetrieveTaskFromTaskListService.java.
- The MongoTaskDocument.java class defines the attributes of a Document for storing a task in the collection
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 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.