I’m developing an api in Fast API using sqlalchemy to manage my ORM classes and operations with the database.
I’m dealing with some design decisions to have my classes as little coupled as possible without losing information.
For example, this is one of my entities called Topic
this class defined in models.py and is supposed to be use by the TopicRepository
class Topic(Base):
__tablename__ = "topics"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, unique=True)
category_id = Column(
Integer, ForeignKey("categories.id", ondelete="CASCADE"), nullable=False
)
category = relationship("Category", back_populates="topic", lazy="subquery")
I have some “core” classes where this are supposed to live in the business layer and is used by the TopicService, for example
class Topic:
def __init__(self, id: int, title: str, cost: int = 0, capacity=0, category=None):
self._id = id
self._title = title
self._category = category
self._capacity = capacity
# properties
# methods for Topic
The presentation layer which is the TopicRouter uses these classes for making the json responses for different kinds of topic request.
schemas.py
class SimpleTopic(BaseModel):
"""Represents a simple topic with just a name"""
name: str
model_config = ConfigDict(from_attributes=True)
class TopicRequest(SimpleTopic):
category: str
class TopicResponse(SimpleTopic):
id: int
category: SimpleCategory = Field(validation_alias="category")
class TopicList(RootModel):
root: List[TopicResponse]
def __iter__(self):
return iter(self.root)
So, my doubts here are:
- Is this kind of separation, ok?
- Should the TopicService know the classes from schemas.py, models.py and topic.py in order to mapped from one to the other?
- ORM objects should not be used as domain classes so in that case, do I need to duplicate each class?
Thanks! 🙂
2
Usually in this type of scenario you have 3 types of objects:
- Data Objects
- Business Objects
- DTOs (Data Transfer Objects)
From these layers there will be mappers that transform data. Consider a case to get data. The data layer will return objects from the persistence store, a mapper will convert them to business objects, then run any business logic, and then send back resultant DTOs to the client by mapping business objects to DTOs. The reverse can also but true that is data coming in versus data coming out. The mappers themselves do the work of mapping the object to its resultant object. So, each layer only deals with the data it knows about, therefore there is good separation. Usually there is a controller, service, repository layer involved as well. This isn’t the only way to do things but may give you a sense of what you’re trying to do. Usually, DTOs have no logic other than setters/getter and maybe data validation to ensure proper data is passed to the service layer, service level objects may have some built in functionality or that may be delegated to service(s), and data objects typically massage service level objects into the current persistent layer schema. This is typically your ORM.
Data should never be lost because the mappers ensure data is passed correctly between the layers. Mapping itself is isolated as the layers of the application delegate that responsibility to the mappers.