Dependency Injection

to write easy-refactorable code

What is DI?

Technique

to get dependencies from outside

dependent doesn't know detail

Clean Architecture

The key is layer and DI

Core don't know how outer worlds works

Common problems

Tightly coupled code

cause problems

database + external services

if we have to migrate database...??

It's hard to refactor

repository pattern + DIĀ 

to make code loose coupled

in most MMT services

"manager" exists

and most of them use model or sql query directly

class FooManager:
    async def get_bar(user_id: str):
        stmt = f"SELECT bar, zee from foo_tbl where bar = {user_id}"
        result = await MySQLSingleton.fetch(stmt)

        return result

Add some abstraction here

Repository

class FooRepository(ABC):
    
    @abstracmethod
    async def get_bar(user_id: str) -> Dict[str, Any]:
        pass

It provides unifed interface to access data

dependents doesn't know where repository data gets from

There is no need to modify the dependents while provide the same interface.

class DynamoFooRepository:
    
    ...
    
    async def get_bar(user_id: str) -> Dict[str, Any]:
        r = await self.model.query(hash_key=user_id)

        return r
        
class MySQLFooRepository:
    
    ...
    
    async def get_bar(user_id: str) -> Dict[str, Any]:
        stmt = "..."
        r = await self.connection.fetch(stmt)

        return r
        

even if data source is not database, it works!

class ExternServiceFooRepository:
    
    ...
    
    async def get_bar(user_id: str) -> Dict[str, Any]:
        r = await service("user").get("...")

        return r
        

Dependency Injection

How can we inject repository to managers?

hard code?

class FooManager:
    repo = FooRepository(...)
    
    async def get_bar(user_id: str) -> Dict[str, Any]:
        r = self.repo(user_id)

        return r

Nope.

it is not loose coupled code

class FooManager:
    def __int__(repo: FooRepositoryABC)
        self.repo = repo
    
    async def get_bar(user_id: str) -> Dict[str, Any]:
        r = self.repo(user_id)

        return r
class FooView(InsanicView):
    manager = FooManager(DynamoFooRepository())
    
    async def get(request: Request, user_id: str):
        r = self.manger.get_bar(user_id)

        return Response(json=r, status_code=200)

Now looks good!

You can now change data source with one line of code

Thanks!