This repository is Proof of concept (POC) to demonstrate a simplified model of a prepaid card. The card holds a balance in GBP and customers can make transactions in GBP.
Sandbox is available here: http://178.62.42.204/. At any time user can reset all sandbox data using a red button from navbar.
As in real life, the Customer and the Merchant in the shop are not focused how the CreditCardProvider deals with the transactions on card internally. So, the application was split for 2 main bounded contexts, which are independent individually:
CoffeeShop:
- Contains Merchant and Customer aggregates and their behavior (buy a product by Customer, authorize a Merchant, capture an authorization etc.)
CreditCard:
- Contains CreditCard aggregate and behavior (load/block/unblock/charge funds)
It's highly possible that in future the responsibility of CreditCard will by moved to an external 3rd party credit card provider like (https://stripe.com, https://www.braintreepayments.com/).
In order to easy switch CreditCardProvider the CreditCard context was completely decoupled from CoffeeShop context.
CoffeeShop context has an interface CreditCardProvider
and current implementation is LocalCreditCardProvider
which is a bridge to CreditCard context.
If the external CreditCardProvider
will be chosen then CreditCardProvider
need to have another implementation like StripeCreditCardProvider
.
- Aggregates were marked with
SimplePrepaidCard\Common\Model\Aggregate
interface, - Value objects with
SimplePrepaidCard\Common\Model\ValueObject
interface, - Domain events with
SimplePrepaidCard\Common\Model\DomainEvent
interface.
Each context of that application (CoffeeShop, CreditCard) has layers:
Application
- This layer coordinates the application activity.Infrastructure
- This layer handles interaction with infrastructure. It contains an implementation of adapters to an infrastructure.Model
- It handles business rules related to the context. It contains aggregates which trigger domain events.
I used SimpleBus/MessageBus to implement CQRS pattern.
Each command has only one responsibility and those responsibilities were described with Gherkin
scenarios:
CoffeeShop context commands:
- Buy a product
- Authorize a merchant
- Capture an authorization
- Reverse an authorization
- Refund captured
CreditCard context commands:
Read model and write model are completely separated.
To expose that fact the write model was realized with Sqlite database and read model with Redis cache.
Each adapter to an infrastructure has an interface extracted and implementation within Infrastructure
layer.
Examples:
StatementQuery
=> RedisStatementQuery
CustomerRepository
=> DoctrineORMRepository
CreditCardProvider
=> LocalCreditCardProvider
It allows me to quickly switch between implementations in a case when I decide for e.g. to use DoctrineODM
instead of DoctrineORM
.
It is helpful for test purposes as well. Here is another implementation od credit card provider TestCreditCardProvider
which allows me to define a behavior of credit card provider for test purposes.
The application has framework agnostic model. The entry point for the application model is Symfony controller within the Bundle. The bundle contains only framework related stuff (views) and configuration of them.
Here is a simple script to firstly provision http://178.62.42.204/ host with Ansible and then deploy the application on it. Each deployment is triggered in automated way after each successful build with Travis CI. I used encrypted ssh key to allow Travis CI deploy my host.
An application has wide test-suite:
composer static-analysis
- (php-cs-fixer was used to fix automatically the broken code standards)composer spec
- (PHPSpec was used specification testing of business model)composer integration
- (PHPUnit was used for test integration with Sqlite database, Redis cache and Symfony framework)composer behat
- (Behat was used for acceptance test of business requirements)composer e2e
- It's a test-suite that simulates using a whole application on production. It's for ensuring that everything is able to work together (framework, two bounded contexts etc)