:football: A microservice to issue, register and manage boletos
Here's a brief overview of our technology stack:
In order to develop for this project you must have Docker and Docker Compose installed.
If you never developed in this repo before:
$ git clone git@github.com:pagarme/superbowleto
$ docker-compose build superbowleto-web
To run the server, you will have to start the database and run the migrations.
$ make setup-db
Start database (postgres):
$ make start-db
Run the migrations:
$ make migrate
$ make superbowleto-web
Tests are separate in functional
, integration
and unit
. You can either run them separately or run them all.
Run all tests:
$ docker-compose run test
Run only functional
tests:
$ docker-compose run test npm run test-functional
Run only integration
tests:
$ docker-compose run test npm run test-integration
Run only unit
tests:
$ docker-compose run test npm run test-unit
For the CI purposes we have a specif command that will generate coverage report and xml test results report to be published at the CircleCI.
$ docker-compose run --entrypoint="npm run test-ci" test --abort-on-container-exit
We install our dependencies (aka npm dependencies) inside the Docker image (see our Dockerfile to understand it better).
This gives us the advantage of caching the dependencies installation process, so when we build the image again, it's already cached by Docker and the image can be easily distributed with all its dependencies installed.
However, if you need to install any new dependency, you must rebuild the image, otherwise, your dependency will not be available inside the container.
You can install dependencies and rebuild the image by running:
$ docker-compose run test npm install --save ramda
$ docker-compose build test
Tests are found inside the test/
directory and are separate by type: functional
, integration
and unit
. It's also common to have some helpers
folders alongside the tests.
Unit
tests are used to test the smallest units of functionality, typically a method or a function ref.
The folder structure of the unit tests tend to mirror the folder structure of the src
folder. For instance, we generally see the following folder structure:
├── src
│ ├── index.js
│ └── lib
│ └── http.js
└── test
└── unit
├── index.js
└── lib
└── http.js
Integration
tests build on unit tests by combining the units of code and testing that the resulting combination functions correctlyref.
The folder structure of the integration tests tend to mirror the folder structure of the src
folder. For instance, we generally see the following folder structure:
├── src
│ ├── index.js
│ └── lib
│ └── http.js
└── test
└── integration
├── index.js
└── lib
└── http.js
Functional
tests check a particular feature for correctness by comparing the results for a given input against the specification. Functional tests don't concern themselves with intermediate results or side-effects, just the resultref.
The folder structure of functional tests does not need to mirror the source folder, and the files can be organized as they seem fit. One way to organize this files is by feature
or user-story
. For instance, take a look at the example below, where boleto/create.js
and boleto/register.js
are complete user stories:
├── test
└── functional
└── boleto
└── create.js
└── register.js
Helpers
do not test anything, but instead provide tools for the tests. Inside the helpers
folders one can have fixtures
(also know as "mocks"), or some util functions.
For instance, if you need credit card information to perform various tests in many different places, or if you need an util function that is called before your tests are ran, you could place them inside a helpers
folder in order to not repeat yourself:
const creditCardMock = {
number: 4242424242424242,
holder_name: "David Bowie",
expiration_date: 1220,
cvv: 123,
};
const cleanUpBeforeTests = () => {
db.reset();
};
module.exports = {
creditCardMock,
cleanUpBeforeTests,
};
Helpers
folders can be created at any level within the test
folder structure. If some helper is used only for unit tests, it should reside within test/unit/helpers
. If the helpers is used across all tests, it should reside within test/helpers
. If there's a helper that is used only for testing the http module on integration tests, then it should reside within test/integration/http/helpers
.
This project has two programs, the worker
and the server
.
This section documents what every endpoint of the server
does.
Create a new boleto.
After creating the boleto (on our database), we will try to register the boleto withing the provider. Here, there's two possible outcomes: a) the provider could be reached, could process the boleto and gave us a status (either registered
or refused
); or b) the provider could not be reached or could not process the boleto (giving us an unknown
/undefined
/try_later
status).
The following steps illustrate the case where the provider could be reached and it could process the boleto.
Client
makes an HTTP request to create a boleto.Database
with status issued
.registered
or refused
).Database
.Client
(HTTP response).Diagram built with mermaid.js. Check out the source code at docs/diagrams/server
The following steps illustrate the case where the provider could be reached and it could process the boleto.
Client
makes an HTTP request to create a boleto.Database
with status issued
.Database
to pending_registration
.boleto_id
and issuer
) to an SQS queue called boletos-to-register
. This queue will be processed by the worker
later.Client
(HTTP response) with the status = pending_registration
.Diagram built with mermaid.js. Check out the source code at docs/diagrams/server
Example:
POST /boletos
Content-Type: application/json
{
"queue_url": "http://yopa/queue/test",
"expiration_date": "Tue Apr 18 2017 18:46:59 GMT-0300 (-03)",
"amount": 2000,
"instructions": "Please do not accept after expiration_date",
"issuer": "bradesco",
"payer_name": "David Bowie",
"payer_document_type": "cpf",
"payer_document_number": "98154524872"
}
201 Created
Content-Type: application/json
{
"queue_url": "http://yopa/queue/test",
"status": "issued | registered | refused",
"expiration_date": "Tue Apr 18 2017 18:46:59 GMT-0300 (-03)",
"amount": 2000,
"instructions": "Please do not accept after expiration_date",
"issuer": "bradesco",
"issuer_id": null,
"title_id": "null",
"payer_name": "David Bowie",
"payer_document_type": "cpf",
"payer_document_number": "98154524872"
}
Retrieve all boletos.
Diagram built with mermaid.js. Check out the source code at docs/diagrams/server
Example:
GET /boletos
Content-Type: application/json
{
"count": "10",
"page": "1"
}
200 Ok
Content-Type: application/json
[{
"id": "bol_cj1o33xuu000001qkfmlc6m5c",
"status": "issued",
"queue_url": "http://yopa/queue/test",
"expiration_date": "Tue Apr 18 2017 18:46:59 GMT-0300 (-03)",
"amount": 2000,
"instructions": "Please do not accept after expiration_date",
"issuer": "bradesco",
"issuer_id": null,
"title_id": "null",
"payer_name": "David Bowie",
"payer_document_type": "cpf",
"payer_document_number": "98154524872"
}]
Find one boleto by id.
Diagram built with mermaid.js. Check out the source code at docs/diagrams/server
Example:
GET /boletos/:id
Content-Type: application/json
{
"id": "bol_cj1o33xuu000001qkfmlc6m5c"
}
200 Ok
Content-Type: application/json
{
"id": "bol_cj1o33xuu000001qkfmlc6m5c",
"status": "issued",
"queue_url": "http://yopa/queue/test",
"expiration_date": "Tue Apr 18 2017 18:46:59 GMT-0300 (-03)",
"amount": 2000,
"instructions": "Please do not accept after expiration_date",
"issuer": "bradesco",
"issuer_id": null,
"title_id": "null",
"payer_name": "David Bowie",
"payer_document_type": "cpf",
"payer_document_number": "98154524872"
}
This section documents what the worker
processes.
boletos-to-register
queueThis is a worker which consumes the queue which has boletos to register and effectively register them.
When a boleto can't be registered within the provider at the moment of its creation, it will be posted to a SQS Queue called boletos-to-register
. This worker is responsible for processing this queue. Here are the steps:
sqs-quooler
we then start to poll items from SQS (sqs.receiveMessage
)boleto
({ id, issuer}) and a message
(the raw SQS message from boletos-to-register
queue).id
to find the boleto on Database
.issued
or pending_registration
.boletos-to-register
queue and will be later processed.registered
or refused
.boleto.queue_url
). The owner will then handle the processing of these SQS Messages. That's the only way we can notify the boleto owner that a boleto that went to boletos-to-register
queue was updated. That's why it's mandatory to pass a queue at the moment of the boleto creation.
Diagram built with mermaid.js. Check out the source code at docs/diagrams/worker