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

    View Slide

  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

    View Slide

  3. WHAT IS THE DEFINITION
    OF ARCHITECTURE?

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  7. D O W E N E E D A R C H I T E C T U R E ?

    View Slide

  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

    View Slide

  9. What is the meaning of clean?

    View Slide

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

    View Slide

  11. Clean Architectures in Python
    A practical approach to better software design
    bit.ly/getpycabook
    Leonardo Giordani

    View Slide

  12. The Clean Architecture
    A layered approach for a more
    civilized age

    View Slide

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

    View Slide

  14. class Item:
    def __init__(self, code, price):
    self.code = code
    self.price = price
    Entities: simple models

    View Slide

  15. Use case: retrieve a list of items
    use_case = uc.ItemsListUseCase()
    use_case.execute()

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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())

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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())

    View Slide

  29. Is it possible to migrate an existing system?

    View Slide

  30. Is this the definitive architecture?

    View Slide

  31. Clean Architectures in Python
    A practical approach to better software design
    bit.ly/getpycabook
    Leonardo Giordani

    View Slide

  32. Harry Percival, Bob Gregory
    www.cosmicpython.com
    Architecture Patterns with Python

    View Slide

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

    View Slide