Storage
Requests
// Account management
POST /users -> User
GET /users/
PATCH /users/
POST /merchants -> Merchant
GET /merchants/
PATCH /merchants/
// Payment
POST /payments -> Payment // could be send or request
GET /payments?q={filters}&pageSize={}&pageToken={}
GET /payments/
// Fund transfer
POST /transfers -> Transfer
User
UserBalance
Merchant
UserPaymentAccounts
Payment
Transfer // balance -> balance transfer)
Transaction
TransactionLog
User and Merchant have separate clients
PaymentService handles user payment to merchants
TransferService handles user to user transfers
Payments are queued, and processed by PaymentProcessor. It adds transactions to a transaction queue, which is processed by TransactionProcessor.
TransactionProcessor calls PaymentNetwork asynchronously. It registers a callback so PaymentNetwork can report status.
All services reports to DB to record payment, transfer, transaction and their logs.
Payment and Transfer services pushes messages to NotificationQueue, which is processed by NotificationService and sends to client.
Account management flow is standard CRUD operation against DB.
Payment request:
Transfer
TransferProcessor
PaymentProcessor
TransactionProcessor
We uses queues to decouple the services due to the asynchronous nature of payment processing, and to improve reliability of the system.
Fund transfers can be processed in one database transactions, therefore we simply do this in one service.
We store all transaction history in DB. This is needed in order to reconcile asynchronous transactions, and for auditing purposes.
For the DB, we choose a ACID relational database for its strong consistency. We need to shard it (see next section).
All services are stateless and can be scaled horizontally.
The database cannot be naively shared since payments and transfers may be may transacted between any 2 users and merchants.. Storage size is not a problem. Assume each payment generate 10 database transactions, 10k tps is on the edge of a well-tuned database.
The database load can be further reduced if we add a caching layer for user profile view, payment views, etc.
Kafka queues: assume each transaction needs 10 messages, our 1k peak qps or 10k message/sec can be easily handled by 1 partition. We can create 3 partitions so we have redundant capacity.
We should have monitoring of resources in place: queues, service CPU/memory. When the system is about to get overloaded, we need to rate limit and apply circuit breaker if necessary.
What are some future improvements you would make? How would you mitigate the failure scenario(s) you described above?