Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Clean Architectures in Python

Clean Architectures in Python

Architectural considerations are often overlooked by developers or completely delegated to a framework. We should start once again discussing how applications are structured, how components are connected and how to lower coupling between different parts of a system, to avoid creating software that cannot easily be maintained or changed.

The “clean architecture” model predates Robert Martin, who recently brought it back to the attention of the community, and is a way of structuring applications that leverages layers separation and internal APIs to achieve a very tidy, fully-tested, and loosely coupled system. The talk introduces the main ideas of the architecture, showing how the layers can be implemented in Python, following the content of the book “Clean Architectures in Python” edited by Leanpub. The book recently reached 8,000 downloads and many readers found it useful to start learning how to test software and how to structure an application without relying entirely on the framework.

Leonardo Giordani

June 16, 2019
Tweet

More Decks by Leonardo Giordani

Other Decks in Programming

Transcript

  1. Clean Architectures in Python A tale of durability, utility, and

    beauty
  2. “Who wrote this code?” L E O N A R

    D O G I O R D A N I S O F T W A R E D E V E L O P E R A N D B L O G G E R W W W. T H E D I G I T A L C AT O N L I N E . C O M @TW_LGIORDANI - @THEDIGICAT
  3. WHAT IS THE DEFINITION OF ARCHITECTURE?

  4. F I R M I T A S , U

    T I L I T A S , V E N U S T A S Vitruvius, De architectura
  5. D U R A B I L I T Y,

    U T I L I T Y, B E A U T Y Vitruvius, De architectura
  6. T H E A R T A N D S

    C I E N C E I N W H I C H T H E C O M P O N E N T S O F A C O M P U T E R S Y S T E M A R E O R G A N I S E D A N D I N T E G R AT E D
  7. D O W E N E E D A R

    C H I T E C T U R E ?
  8. Ivar Jacobson (1992) Object Oriented Software Engineering: A Use-Case Driven

    Approach E. Gamma, R. Helm, R. Johnson, J. Vlissides (1994) Design Patterns Robert Martin (2000) Design Principles and Design Patterns Eric Evans (2003) Domain-Driven Design: Tackling Complexity in the Heart of Software H. Hohpe, B. Woolf (2003) Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions
  9. What is the meaning of clean?

  10. You know where things are, why components are there, what

    something is.
  11. Clean Architectures in Python A practical approach to better software

    design bit.ly/getpycabook Leonardo Giordani
  12. The Clean Architecture A layered approach for a more civilized

    age
  13. The golden rule Talk inward with simple structures, talk outwards

    through interfaces.
  14. class Item: def __init__(self, code, price): self.code = code self.price

    = price Entities: simple models
  15. Use case: retrieve a list of items use_case = uc.ItemsListUseCase()

    use_case.execute()
  16. @blueprint.route('/items', methods=['GET']) def items(): pass We want to build a

    web application
  17. @blueprint.route('/items', methods=['GET']) def items(): use_case = uc.ItemsListUseCase() use_case.execute(request.args) Incoming HTTP

    requests become a call and simple structures
  18. @blueprint.route('/items', methods=['GET']) def items(): use_case = uc.ItemsListUseCase() use_case.execute(request.args) The use

    case extracts data from a repository, which can be any source of data
  19. @blueprint.route('/items', methods=['GET']) def items(): use_case = uc.ItemsListUseCase() use_case.execute(request.args) And the

    repository can be accessed through an interface
  20. @blueprint.route('/items', methods=['GET']) def items(): repo = PostgresRepo(CONNECTION_STRING) use_case = uc.ItemsListUseCase(repo)

    use_case.execute(request.args) The use case receives the repository interface as an argument of the call
  21. class ItemsListUseCase: def __init__(self, repo): self.repo = repo def execute(self,

    params): # BUSINESS LOGIC HERE result = self.repo.list(params) # BUSINESS LOGIC HERE return result The use case queries the repository interface with simple structures
  22. class PostgresRepo: def __init__(self, CONNECTION_STRING): self.ng = create_engine( CONNECTION_STRING) Base.metadata.bind

    = self.ng def list(self, filters): DBSession = sessionmaker(bind=self.ng) session = DBSession() query = ... The database interface and the database exchange data in a specific language
  23. The database interface translates the specific language into simple structures

    and entities class PostgresRepo: def __init__(self, CONNECTION_STRING): self.ng = create_engine( CONNECTION_STRING) Base.metadata.bind = self.ng def _create_items(self, results): return [Item(code=q.code, price=q.price) for q in results] def list(self, filters): DBSession = sessionmaker(bind=self.ng) session = DBSession() query = ... return self._create_items(query.all())
  24. @blueprint.route('/items', methods=['GET']) def items(): repo = PostgresRepo(CONNECTION_STRING) use_case = uc.ItemsListUseCase(repo)

    result = use_case.execute(request.args) The use case returns the result of the business logic: entities and simple structures
  25. @blueprint.route('/items', methods=['GET']) def items(): repo = PostgresRepo(CONNECTION_STRING) use_case = uc.ItemsListUseCase(repo)

    result = use_case.execute(request.args) return Response( json.dumps(result), mimetype='application/json', status=200) The web framework converts entities and simple structures into HTTP responses
  26. class ItemsListUseCase: def __init__(self, repo): self.repo = repo def execute(self,

    params): # BUSINESS LOGIC HERE result = self.repo.list(params) # BUSINESS LOGIC HERE return result Testing the use case
  27. @blueprint.route('/items', methods=['GET']) def items(): repo = PostgresRepo(CONNECTION_STRING) use_case = uc.ItemsListUseCase(repo)

    result = use_case.execute(request.args) return Response( json.dumps(result), mimetype='application/json', status=200) Testing the HTTP endpoint
  28. Testing the repository interface: integration test class PostgresRepo: def __init__(self,

    CONNECTION_STRING): self.ng = create_engine( CONNECTION_STRING) Base.metadata.bind = self.ng def _create_items(self, results): return [Item(code=q.code, price=q.price) for q in results] def list(self, filters): DBSession = sessionmaker(bind=self.ng) session = DBSession() query = ... return self._create_items(query.all())
  29. Is it possible to migrate an existing system?

  30. Is this the definitive architecture?

  31. Clean Architectures in Python A practical approach to better software

    design bit.ly/getpycabook Leonardo Giordani
  32. Harry Percival, Bob Gregory www.cosmicpython.com Architecture Patterns with Python

  33. Thank you! @tw_lgiordani - @thedigicat bit.ly/getpycabook - thedigitalcatonline.com