0

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

# 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?

7
  • please don't cross post: stackoverflow.com/questions/79096746/…
    – gnat
    Commented Oct 17 at 14:39
  • @gnat should I delete post on stackoverflow? I was suggested to post here as you could notice in comments Commented Oct 17 at 15:01
  • The question is not quite on topic for either site. It appears to be both a concrete coding problem (which is on-topic for StackOverflow provided you include a minimal reproducible example) and a conceptual design problem (which could be on-topic for this community provided you aren't looking for help writing code). Can you edit your question to make the design problem you are trying to solve more clear? Then I would advise you to delete the question on StackOverflow. Commented Oct 17 at 15:16
  • 1
    That definitely helps. We still don't want the same question in multiple communities. Please delete your question on StackOverflow. Commented Oct 17 at 16:43
  • 1
    I don't understand the dilemmas you're trying to describe, due to the English used. Maybe try to rewrite the dilemmas as a question?
    – DarkTrick
    Commented Oct 20 at 4:34

1 Answer 1

2

I'm not sure where the dilemma is. Of course the repository should encapsulate the options and anything else database related. Returning only fully populated domain objects.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.