The app uses in memory storage.
The application is listening to port 8085, and:
The application is able to receive a
POST http://localhost:8085/api/register
with payload (store it as a shipment
):
{
"reference":"ABCD123456",
"parcels" : [
{
"weight":1,
"width": 10,
"height": 10,
"length": 10
},
{
"weight":2,
"width": 20,
"height": 20,
"length": 20
}
]
}
The application is able to receive a
PUT http://localhost:8085/api/push
with the following payloads (tracking
):
{
"status":"WAITING_IN_HUB",
"parcels":2,
"weight":null,
"reference":"ABCD123456"
}
{
"status":"WAITING_IN_HUB",
"parcels":2,
"weight":2,
"reference":"ABCD123456"
}
{
"status":"WAITING_IN_HUB",
"parcels":1,
"weight":15,
"reference":"ABCD123456"
}
{
"status":"WAITING_IN_HUB",
"parcels":2,
"weight":30,
"reference":"ABCD123456"
}
{
"status":"DELIVERED",
"parcels":2,
"weight":2,
"reference":"ABCD123456"
}
{
"status":"DELIVERED",
"parcels":2,
"weight":30,
"reference":"ABCD123456"
}
{
"status":"DELIVERED",
"parcels":2,
"weight":30,
"reference":"EFGH123456"
}
{
"status":"DELIVERED",
"parcels":null,
"weight":30,
"reference":"ABCD123456"
}
Using the above examples:
Given the provided shipment
When
shipment
reference is equal totracking
referenceshipment
parcel number is equal totracking
parcel number.shipment
total weight is less thantracking
weight.tracking
status isDELIVERED
Then dispatches an application event
{
"reference":"ABCD123456",
"status": "CONCILLIATION_REQUEST"
}
AND prints it into the console
Given the provided shipment
When
shipment
reference is equal totracking
reference.shipment
parcel number is equal totracking
parcel number.shipment
total weight is greater or equal thantracking
weight.tracking
status isDELIVERED
.
Then dispatches an application event
{
"reference":"ABCD123456",
"status": "NOT_NEEDED"
}
AND print it into the console
Given the provided shipment
When
shipment
reference is equal totracking
referencetracking
status is notDELIVERED
Then dispatches an application event
{
"reference":"ABCD123456",
"status": "INCOMPLETE"
}
AND print it into the console
Given the provided shipment
When
shipment
reference is equal totracking
reference- any other
tracking
field is null
Then dispatches an application event
{
"reference":"ABCD123456",
"status": "INCOMPLETE"
}
AND print it into the console
Given the provided shipment
When
tracking
reference is not equal toshipment
reference
Then dispatches an application event
{
"reference":"EFGH123456",
"status": "NOT_FOUND"
}
AND print it into the console
#Instructions & reasoning
#####Dependencies The language chosen for the implementation is Scala, so this repo assumes you have Scala Build Tool (sbt) installed on your machine. If you don't have it please follow the instructions in their website to do so.
#####Chosen language and frameworks The application server is built on top of Akka for the main HTTP server. It has a bit of further dependencies and bindings in order to ensure proper route modelling and testing.
Once you have cloned the repo, execute
sbt test
to execute the unit tests- or
sbt run
to bring up the API and test it yourself
#Status of the challenge The desired functionality for the test is mostly done, just lacking some testing configuration so the tests don't fail when run concurrently. More on this on the "testing" section here.
###My understanding of the challenge instructions
I interpreted Shipment
and Tracking
to be two different entities that can be included anytime by HTTP requests idempotently.
Provided that all bussiness cases descriptions began with
Given the provided
shipment
I understood that Shipment
was the originator of the application event provided the possible already existing Traking
s.
At the same time, I understood that the business logic can happen only once per Shipment
and Tracking
coincidence, and only when the Shipment
is being registered.
This way, the Tracking
pusher only stores the value to the corresponding InMemory repository.
I also understood the term application event
as a Domain Event, which triggers the console printout separatedly
from the use case.
###Technical reasons behind some choices
#####Domain driven design principles
Most of the application is held inside the "core" bounded context, although some of the functionality not inherent to the domain (the challenge) is put inside the "shared kernel" module.
There's the usage of a layered architecture so as to decouple infrastructure (api and other tools) from the business logic (domain), and to have business use cases defined (application).
As the instructions of the code challenge didn't indicate any state change on both Shipments
and Trackings
, I treat both units as Value Objects being stored in a persistence mechanism.
As most the application is plenty of side-effects, I tried to use the Reader Monad which is implemented in Cats to solve Dependency Injection in a functional way. As I'm not an expert on FP, this is for the mere fact of proving interest on functional programming, and the the api is not fully functional.
There are Unit,Integration,and End to End tests in the test module of the challenge. In order to differentiate between them, you can search in the challenge files by typing "Unit|Integration|E2E" in your IDE of choice so as to find the files in it.
There are different flavors of testing, but the E2E tries to mimic the business requirements, following Gherkin notation.
Important: There's a problem with the tests as they are being executed concurrently and some of them may fail when run from the same suite. Execute them separatedly by their package (e2e,infrastructure,module) and they'll pass.
#What I could have done with more time
Provided the time I got to do the test (weekend) I would have liked to add completeness to the challenge with the following thigs:
- Being able to get
InMemoryRepository
runtime values in order to do background checks for the matches on E2E tests. - Being able to capture part of the logs and stdout of the application to use them in E2E tests.
- Proper Domain Event -> Event Listener binding, in a Trait or some abstraction so the knowledge is not in the event itself
- On this topic, also firing the events with the Actor Model so as to decouple event listening from code execution.