The application was implemented with .NET Core 3.1 SDK and .NET Standard 2.0.

How to run locally?

Run the application as a whole.

After running the command below, the API can be accessed via http://localhost:3015/

docker-compose -f docker-compose.yml up --remove-orphans --force-recreate --renew-anon-volumes --build --abort-on-container-exit

The datastore for local development

docker-compose -f docker-compose.development.yml up --remove-orphans --force-recreate --renew-anon-volumes --build --abort-on-container-exit

Decisions on the development side

The validation was implemented with Fluent Validation.

The sample query:

curl --location --request POST 'http://localhost:3015/api/PaymentGateway' \

--header 'Authorization: 285c0a57-d2f7-47fc-9e54-fc61d0d15fa3' \

--header 'Content-Type: application/json' \

--data-raw '{

"amount": 0.1,

"currency": 1,

"merchant": "Amazon",

"cardDetails": {

"cardNumber": "5500000000000004",

"cardExpiryYear": 2020,

"cardExpiryMonth": 12,

"cvv": 111

}

}'

The API Abstraction Layer

It was created to facilitate possible integration into the system. It only contains request and response models. It can be useful when building an API client with Refit .

The API was documented with the Open API (Swagger) support.

The Application Logging

The console logging provider was used but can be integrated with ELK or another external provider (e.g. GrayLog).

The Application Metrics (APM)

app-metrics.io was used to serve APM metrics with the url http://localhost:3015/metrics. It can be integrated into Prometheus and Grafana.

Authentication.

It was implemented using API key logic, but the list of keys was provided by every external vault system. Saving the API keys directly is not recommended unless they are not rotated frequently during the Vault integration.

HashiCorp's Vault, Azure Key Vault, and AWS Key Management Service may be better suited for production because of the rotation support.

The most reliable authentication system can be OAuth or JWT standards. It can be expired, updated, etc.

Encryption.

The card details were encrypted before being written to the database. It was serialized using BinaryFormatter. MongoDb also supports client-side encryption but it is not a good practice to be dependent on the data store that created a tightly coupled system.

In production, the encryption key can be protected with Kubernetes Secrets and passed as an environment variable.

The Bank Simulator

It was implemented with Express.js to make the possible integration easy later.

When calling the bank simulator API the issues were handled with Polly Advanced (to have a circuit breaker) and Jitter (to avoid retries bunching into further spikes of load) retry policies. To make external integration easier and clearer, Refit was used.

The Domain Layer

It resesents the concerpt of business and follows the Persistence Ignorance and the Infrastructure Ignorance principles. The entities at this layer are POCO and do not contain an attribute.

The infrastructure layer

This layer contains the implementation of data persistence and repository.

Note. There are some important cases left in order to be implemented in the future:

  1. Instead of feeding data from the external API, it can be better to have the publish/subscriber pattern. When something has changed on the show and cast side they can publish an event that will be executed by a subscription in the reading model. It can also be the same as the CQRS implementation.
  2. The index is missing in MongoDB.
  3. The application was not covered with the unit tests as a whole due to the time limitation.
  4. The payment gateway system can be implemented more resiliently through the combination Sagas, event sourcing and the help of a broker to address the lack of transactions in a distributed system..
  5. And so on...