Skip to content

jinhoyoo/breadpan

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

91 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Breadpan

The framework implemented the architecture by following 'Clean architecture' in python.

BreadPan

This implementation includes:

  • The basic application structure to apply 'clean architecture' concepts including presenter, controller, interactor, and data gateway
  • The example of RESTful API server

And it didn't include:

  • No real implementation of data gateway for RDBMS or NoSQL DB

Basic architecture

@startuml

package breadpan.entity <<Frame>> {
    class Entity
}

package breadpan.usecase <<Frame>> {

    UsecaseInputPort ..> Entity
    UsecaseInputPort <|-- UsecaseInteractor
    UsecaseInteractor ..> UsecaseOutputPort
}

package breadpan.interface <<Frame>> {
  Controller ..> UsecaseInteractor
  Controller ..> Presenter
  Presenter --|> UsecaseOutputPort
  UsecaseInteractor ..> DataAccessGateway
}

@enduml

BreadPan has three layers.

  • entity : The layer that has the entities to represent the information structure
  • usecase : The layer that embraces the business logic
  • interface : The layer that handles the out side resources including external APIs, database, and peripherals.

You can design the system architecture by following these layering rules. I understand the key ideas of clean architectures are

  1. Define the information clearly.
  2. Separate the internal and external modules to test internal module without any dependency of external modules.
  3. (This is my idea from the experience) You can make redundency interface and modules for easy maintanance but don't allow the reduendency of functions and constant by copy and paste.


Getting started by example, to-do management

1. Installation

You can install the breadpan package like the following.

pip install breadpan

2. Define the entities

If you design the software architecture, you'll begin from the 'defining of information structure'. The information is composed from the data. The entityis the single unique object in the real world that is being mastered. (From this document)

With breadpan, you can define the entity by inherited from the breadpan.entity like the following.

from breadpan.entity import Entity

class ToDoEntity(Entity):
    """Example of data entity class for ToDo
    """
    def __init__(self, todo_id:str, task:dict):
        """Contructor

        Arguments:
            Entity {Entity} -- Base entity class
            todo_id {str} -- ID of todo item. Linked to entity_key.
            task {dict} -- Contents of todo task.
        """
        self.entity_key = todo_id
        self.task = task

3. Implement the usecase layer

As a to-do management, we need five operations at least.

  • Create a new to-do item
  • Read to-do user made
  • List up to-do items
  • Update to-do item
  • Delete to-do item

These operations are very independent from any views or external database or APIs. So we can make abstract interface and we call it DataAccessGateway. Then we can separate real operation and external interface with breadpan.UsecaseInteractor like the following.

from breadpan.entity import UsecaseInteractor, UsecaseOutputPort
from todo.entity import ToDoEntity

# Operation: Create a new `to-do` item
class ToDoCreateInteractor(UsecaseInteractor):
    def run(self, database: DataAccessGateway):

        # Get id from the controller's data.
        todo_id = self.input["todo_id"]
        contents = self.input["contents"]

        # Create TodoEntity.
        t = ToDoEntity(todo_id, contents['task'])

        # Store the data into data base.
        database.create(t)

        # Link to output port
        return UsecaseOutputPort(todo=t) #Expose the data 't' with 'todo' as key.

The UsecaseInteractor has the special member variable, input. This member variable has all of data that you put into the interactor class. This code shows how you can put the data into this interactor class.

from breadpan.interface import DataAccessGateway
import ToDoCreateInteractor

# Inherite the DataAccessGateway and implement database operations
class RealDatabaseGateway (DataAccessGateway):
    .....


data = RealDatabaseGateway() 
i = ToDoCreateInteractor(todo_id=todo_id, contents=contents)
result = i.run(data)

4. Implement the the interface layer - Controller and Presenter

The Controller is a very special module because it is the bridge between the external input or peripheral and Interactor . It composes the right DataGateway and Interactor's operations. And you can apply some additional operations by changing requirements or policy. So the Controller can be a little bit dirty.

The Presenter is the bridge between Controller and View. If you need to filter out or add more data by changing requirements from customer, you can add 'something' here. So the Presenter can be a little bit dirty too.

For example, if you implement the controller for to-do management, then you can do the following.

from breadpan.interface import Controller
from todo.entity import ToDoEntity
from todo.usecase import DataAccessGateway, ToDoCreateInteractor

class ToDoPresenter(Presenter):
    def show(self):
        todo_entry = self.output['todo']
        return { todo_entry.entity_key : {'task':todo_entry.task}  }

class ToDoController(Controller):
    def __init__(self):
        self.__data = TodoDataInMemory() # Datagateway  module for To-Do (in-memory DB)

    def create(self, todo_id, contents):
        i = ToDoCreateInteractor(todo_id=todo_id, contents=contents)
        return ToDoPresenter(i.run(self.__data)).show()

    ......

If you use this controller and presenter with the Flask, you can do like the following.

import todo
todoCtrl = todo.ToDoController()

class FlaskTodoListController(Resource):

    def post(self):
        args = parser.parse_args()
        all_data = todoCtrl.read_all_data()

        todo_id = len(all_data) + 1
        todo_id = 'todo%i' % todo_id
        task = {'task': args['task']} 

        todoCtrl.create(todo_id, task)
        return task, HTTPStatus.CREATED

.......

api.add_resource(FlaskTodoController, '/todos/<todo_id>')

5. Implement the the interface layer - DataAccessGateway

The DataAccessGateway is most important module. Because it handles all of external interfaces like database, cache or APIs.

For the unit test, DataAccessGateway can be the 'mock' or external module. For example, if you make the mock to pretend the database with dictionary, you can implement like the following.

from todo.entity import ToDoEntity
from todo.interface import DataAccessGateway

class TodoDataInMemory(DataAccessGateway):
   """ TodoDataInMemory
   Store ToDoEntity as {key, value}:=>{todo_id, task}.
   """
   def __init__(self):
       self.TODOS = {}

   def create(self, entity: ToDoEntity):
       self.TODOS[entity.entity_key] = entity.task
       return

   def read(self, entity_id) -> ToDoEntity:
       return ToDoEntity(entity_id, self.TODOS[entity_id])

   def read_all(self):
       return [ ToDoEntity(key, value) for key, value in self.TODOS.items() ]

   def update(self, entity: ToDoEntity, **kwargs):
       self.TODOS[entity.entity_key] = entity.task
       return

   def delete(self, entity_id: str):
       del self.TODOS[entity_id]
       return

Also you can implement DataAccessGateway for MySQL like the following. (I'll skip the concrete implementation.)

from todo.entity import ToDoEntity
from todo.interface import DataAccessGateway

class TodoDataForMySQL(DataAccessGateway):
    """ TodoDataInMemory
    Store ToDoEntity as {key, value}:=>{todo_id, task}.
    """
    def __init__(self):
        # connect MySQL.

    def create(self, entity: ToDoEntity):
        # put todo entity into DB.

    def read(self, entity_id: str) -> ToDoEntity:
        # read todo entity from DB.

    def read_all(self):
        # read todo entities from DB.

    def update(self, entity: ToDoEntity, **kwargs):
        # update todo entity from DB.

    def delete(self, entity_id: str):
        # delete 

7. Implement the the interface layer - View

If you implements general web service, you can make view with HTML template. It depends on the web service framework. And if you make the RESTful API server, then the view module returns the JSON object. So I decided not to design the view module.


Demo project


More reference to read


About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •