The document discusses clean architecture principles in Python, emphasizing the reduction of technical debt by minimizing dependencies. It illustrates an implementation of an account-to-account transfer system using classes for accounts, transfers, and repositories. The document also highlights challenges such as increased complexity and the need to follow architectural rules while providing resources for further reading.
Example
1. Check Balancein Account 1
2. Initiate Transaction
3. Debit Account 1
4. Credit Account 2
5. Record Transfer
6. Commit Transaction
An Implementation of Account to Account Transfer
4 . 1
UseCase
from core.entities importTransfer
from db.repository import TransferRepository
class TransferUseCase:
def __init__(self, repository: TransferRepository): # Dependency... Injected.
self.repository = repository
def create(self, transfer: Transfer):
if transfer.from_account.validate()
and transfer.to_account.validate()
and transfer.from_account.has_sufficient_balance(transfer.amount)
and self._validate_transfer_request(transfer):
with self.repository.atomic():
transfer = self.repository.save(transfer)
return transfer
def _validate_transfer_request(self, transfer: Transfer):
pass
4 . 3
9.
Data Adapters
from typingimport NamedTuple # Don't Type Hints make your code look great?
from core.entities import Transfer
from core.usecase import TransferUseCase
from db.repository import TransferRepository
class AccountData(NamedTuple):
name: str
balance: float
class TransferData(NamedTuple):
from_account: AccountData
to_account: AccountData
amount: float
transaction_date: str
4 . 4
10.
Interface Adapters
from typingimport NamedTuple
from core.entities import Transfer
from core.usecase import TransferUseCase
from db.repository import TransferRepository
class TransferAdapter:
def __init__(self, repository: TransferRepository): # Some more... Injection.
self.usecase = TransferUseCase(repository)
def create(self, transfer_data: TransferData) -> TransferData:
transfer = self._data_to_transfer(transfer_data)
transfer = self.usecase.create(transfer) # Warning: No Exceptions Please!
return self._transfer_to_data(transfer)
@classmethod
def _transfer_to_data(cls, transfer: Transfer) -> TransferData:
pass
@classmethod
def _data_to_transfer(cls, transfer_data: TransferData) -> Transfer:
pass
4 . 5
11.
Repository
from typing importContextManager
from core.entities import Transfer
class TransferRepository:
def save(self, transfer: Transfer) -> Transfer:
# And Finally...
# Persist Transfer AND Accounts
raise NotImplementedError()
def atomic(self) -> ContextManager:
raise NotImplementedError()
4 . 6
12.
Views
import json
from flaskimport request
from flask_restful import Resource, Api
from core.adapters import TransferAdapter
from db.repository import TransferRepository
class TransferResource(Resource):
def __init__(self, *args, **kwargs):
self.super().__init__(*args, **kwargs)
self.adapter = TransferAdapter(TransferRepository()) # Inject... Dependency
@app.route('/api/v1.0/transfer', methods=['POST'])
def post(self):
transfer_data = self.adapter.create(request.form['data'])
return transfer_data, 201 # Again, No Exceptions, Please!
4 . 7
13.
Cons
Everyone needs torespect rules
Difficult to leverage frameworks
Cannot take advantage of Active Record pattern
Can result in Significant Boilerplate
Higher Complexity
There ain't no such thing as a Free Lunch
5
14.
The Principle of
LastResponsible Moment
A good architecture allows you to defer critical decisions
6 . 1
15.
Resources
The Clean Architecture-
Robert C. Martin's Clean Architecture -
Alistair Cockburn -
Clean Architecture Python Apps -
Clean Architecture is Screaming -
Github Repo -
8thlight.com
Amazon.com
Hexagonal Architecture
@haxoza
DZone
Clean Transfer
All Icons are courtesy of the good folks at The Noun Project
6 . 2