Skip to main content
added 108 characters in body
Source Link

I'm trying to decide how to hide data layer details (joinedload of sqlalchemy) in clean architecture.

I have some dilemma about clean architecture. Please look at the code of some api endpoint (full code in https://github.com/albertalexandrov/clean-architecture-sqlalchemy-details, framework - FastAPI):

I have some dilemma about clean architecture. Please look at the code of some api endpoint (full code in https://github.com/albertalexandrov/clean-architecture-sqlalchemy-details, framework - FastAPI):

I'm trying to decide how to hide data layer details (joinedload of sqlalchemy) in clean architecture.

I have some dilemma about clean architecture. Please look at the code of some api endpoint (full code in https://github.com/albertalexandrov/clean-architecture-sqlalchemy-details, framework - FastAPI):

Source Link

SQLAlchemy (ORM) details in service layer in Clean Architecture?

I have some dilemma about clean architecture. Please look at the code of some api endpoint (full code in https://github.com/albertalexandrov/clean-architecture-sqlalchemy-details, framework - FastAPI):

# api/get_user.py

from fastapi import Depends
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app import app
from models.users import User
from repositories.users import UsersRepository


class Schema(BaseModel):
    class ProfileSchema(BaseModel):
        id: int
        age: int
        hobby: str
        address: str

    id: int
    first_name: str
    last_name: str
    username: str


class Repository:
    """This endpoint repository that incapsulates getting user with options.
    I had to create it because I had to use selectinload that is a detail of data layer (of SQLAlchemy)
    and according to clean architecture service should not know about it
    """

    def __init__(self, session: AsyncSession = Depends()):
        self._session = session
        self._users_repository = UsersRepository(session)

    async def get_user(self, user_id: int):
        options = [selectinload(User.profile)]
        return await self._users_repository.get_by_pk(user_id, options)


class Service:
    """This endpoint service for demo purposes. It can be much more complex"""

    def __init__(self, repository: Repository = Depends()):
        self._repository = repository

    async def get_user(self, user_id: int):
        # some logic
        user = await self._repository.get_user(user_id)
        # some logic
        return user

    # another methods


@app.get("/user/{user_id}", response_model=Schema)
async def get_user(user_id: int, service: Service = Depends()):
    return await service.get_user(user_id)

options is SQLAlchemy instruction how to select related data (via sql join or make select in).

I split code into framework layer (this is view, @app.get(...)), service layer (business logic) and data layer (SQLAlchemy, repositories).

The users_repository:

class BaseRepository:
    model = None  # SQLAlchemy model

    def __init__(self, session: AsyncSession = Depends()):
        self._session = session

    async def get_by_pk(self, pk, options=()):
        return await self._session.get(self.model, pk, options=options)

    # another methods


class UsersRepository(BaseRepository):
    model = User

Please take a look at Repository.__init__. As it is written there I did like there because I needed to hide details of SQLAlchemy from services layer (need to specify options param to select related data).

If I do like this:

 class Service:
    """This endpoint service for demo purposes. It can be much more complex"""

    def __init__(self, users_repository: UsersRepository = Depends()):
        self._users_repository = users_repository

    async def get_user(self, user_id: int):
        # some logic
        options = [selectinload(User.profile)]
        user = await self._users_repository.get_by_pk(user_id, options)
        # some logic
        return user

    # another methods

then service layer knows about details of SQLAlchemy (how data layer works exactly).

The dilemma is 1) to create Repository to specify options inside Repository.get_user method (thus service layer does not know details about SQLAlchemy) or 2) to specify options inside Service.get_user (then service layer knows about details of SQLAlchemy).

For know I create Repository if I need to specify options. Is that right?

So how to handle such situations?