DAL
A data access layer (DAL) in computer software is a layer of a computer program which provides simplified access to data stored in rational \ not-rational \ else storage.
Simplified interface of the data for the system logic.
Models definitions, objects attributes and structure, dbs to handle models storage.
Sub objects \ patterns
DB handlers
Db connection handlers, implment the technology language specific use.
Example: Using python sqlite library for connection and execution.
Code Example
Abstract db handler:
from abc import abstractmethod, ABC
class DBHandler(ABC):
def __init__(self):
pass
@abstractmethod
def connect(self):
raise NotImplementedError
@abstractmethod
def disconnect(self):
raise NotImplementedError
@property
@abstractmethod
def connection(self):
raise NotImplementedError
@property
@abstractmethod
def session(self):
raise NotImplementedError
Concrete db technology handler:
from sqlalchemy import create_engine, Engine
from sqlalchemy.orm import sessionmaker, Session
from dal_example.dal.db.db_handler import DBHandler
class SqlAlchemyConsts:
URL = "url"
class SqlAlchemyHandler(DBHandler):
def __init__(self, **kwargs):
super().__init__()
self.__connection_kwargs = kwargs
self.__connection_string = self.__connection_kwargs.pop(SqlAlchemyConsts.URL, None)
self._connection: Engine | None = None
self._session_maker: sessionmaker | None = None
def connect(self):
self._connection = create_engine(self.__connection_string, **self.__connection_kwargs)
self._session_maker = sessionmaker(bind=self.connection)
def disconnect(self):
self._connection.dispose()
@property
def connection(self) -> Engine:
return self._connection
@property
def session(self) -> Session:
return self._session_maker()
Models
The classes to represent the objects structure in the db.
With orm \ odm dessign the models also defines the tables \ collections structure in th DB.
Example code
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Notification(Base):
__tablename__ = 'notifications'
id = Column(Integer, primary_key=True)
message = Column(String, nullable=False)
user_id = Column(Integer, nullable=False)
DAO
Data access object, for each model there is a dao object that his responsibility to hold the connection between the db and the real life objects in the project.
Contains query logic and query building process, working with the database relevant handler.
Code Example
Abstract dao:
import uuid
from abc import ABC, abstractmethod
from typing import Generic, TypeVar
from pydantic import BaseModel
from dal_example.dal.db.db_handler import DBHandler
Object = TypeVar('Object')
DbHandler = TypeVar('DbHandler', bound=DBHandler)
class DataAccessObject(ABC, Generic[Object, DbHandler]):
def __init__(self, db_handler: DbHandler):
self._db_handler = db_handler
self.connect()
@property
def db_handler(self) -> DbHandler:
return self._db_handler
def connect(self):
self.db_handler.connect()
def disconnect(self):
self.db_handler.disconnect()
@abstractmethod
def create(self, new_object: Object) -> Object:
raise NotImplementedError
@abstractmethod
def read(self, object_id: uuid) -> Object:
raise NotImplementedError
@abstractmethod
def update(self, object_id: uuid, updated_object: Object) -> Object:
raise NotImplementedError
@abstractmethod
def delete(self, object_id: uuid) -> None:
raise NotImplementedError
Concrete tech oriented dao:
import uuid
from typing import TypeVar, Type
from dal_example.dal.dao.dao import DataAccessObject, DbHandler
from dal_example.dal.db.db_handlers.sql_alchemy_handler import SqlAlchemyHandler
DeclarativeBase = TypeVar('DeclarativeBase')
class SqlAlchemyDAO(DataAccessObject[DeclarativeBase, SqlAlchemyHandler]):
def __init__(self, db_handler: DbHandler, model_class: Type[DeclarativeBase]):
super().__init__(db_handler)
self.model_class = model_class
def create(self, new_object: DeclarativeBase) -> DeclarativeBase:
with self.db_handler.session as session:
session.add(new_object)
session.commit()
session.refresh(new_object)
return new_object
def read(self, object_id: uuid) -> DeclarativeBase:
with self.db_handler.session as session:
return session.query(self.model_class).filter_by(id=object_id).first()
def update(self, object_id: uuid, updated_object: DeclarativeBase) -> DeclarativeBase:
with self.db_handler.session as session:
db_object = session.query(self.model_class).filter_by(id=object_id).first()
if db_object:
for key, value in updated_object.__dict__.items():
setattr(db_object, key, value)
session.commit()
session.refresh(db_object)
return db_object
def delete(self, object_id: uuid) -> None:
with self.db_handler.session as session:
db_object = session.query(self.model_class).filter_by(id=object_id).first()
if db_object:
session.delete(db_object)
session.commit()
Concrete object dao:
from typing import Type
from dal_example.dal.dao.generic_daos.sql_alchemy_dao import SqlAlchemyDAO
from dal_example.dal.db.db_handler import DBHandler
from dal_example.dal.db.db_handlers.sql_alchemy_handler import SqlAlchemyHandler
from dal_example.dal.models.notification import Notification
class NotificationDAO(SqlAlchemyDAO[Notification]):
def __init__(self, db_handler: DBHandler):
# Pass both db_handler and Notification model to the parent constructor
super().__init__(db_handler, Notification)
def find_by_user_id(self, user_id: int) -> list[Notification]:
"""Find all notifications for a specific user."""
with self.db_handler.session as session:
notifications = session.query(Notification).filter_by(user_id=user_id).all()
return notifications
def get_all_notifications(self) -> list[Notification]:
with self.db_handler.session as session:
notifications = session.query(Notification).all()
return notifications
REPOSITORY
Highest part in the dal pattern, implment data related logic and advanced analyze of the data in order to externalize facade for the business logic layer.
Code Example
Abstract repository:
import uuid
from abc import ABC, abstractmethod
from typing import TypeVar, Generic
from dal_example.dal.dao.dao import DataAccessObject
Object = TypeVar("Object")
Dao = TypeVar('Dao', bound=DataAccessObject)
class Repository(ABC, Generic[Object, Dao]):
def __init__(self, dao: Dao) -> None:
self._dao = dao
@abstractmethod
def create(self, new_object: Object) -> Object:
raise NotImplementedError
@abstractmethod
def read(self, object_id: uuid) -> Object:
raise NotImplementedError
@abstractmethod
def update(self, object_id: uuid, updated_object: Object) -> Object:
raise NotImplementedError
@abstractmethod
def delete(self, object_id: uuid) -> None:
raise NotImplementedError
@property
def dao(self) -> Dao:
return self._dao
Concerete object repository:
import uuid
from dal_example.dal.dao.daos.notification_dao import NotificationDAO
from dal_example.dal.db.db_handlers.sql_alchemy_handler import SqlAlchemyHandler
from dal_example.dal.models.notification import Notification
from dal_example.dal.repository.repository import Repository
class NotificationRepository(Repository[Notification, NotificationDAO]):
def create(self, new_object: Notification) -> Notification:
return self.dao.create(new_object)
def read(self, object_id: uuid) -> Notification:
return self.dao.read(object_id)
def update(self, object_id: uuid, updated_object: Notification) -> Notification:
return self.dao.update(object_id, updated_object)
def delete(self, object_id: uuid) -> None:
self.dao.delete(object_id)
def get_longest_notification(self) -> Notification:
notifications = self.dao.get_all_notifications()
return max(notifications, key=lambda notification: len(notification.message))
def user_has_notifications(self, user_id: int) -> bool:
notifications = self.dao.find_by_user_id(user_id)
return len(notifications) > 0
def get_all_user_notifications(self, user_id: int) -> list[Notification]:
return self.dao.find_by_user_id(user_id)
Full code example
Advanced subjects
ORM
Schemas
Comming soon….