Принятие шаблона репозитория для расширенной серверной разработки с помощью FastAPI
10 февраля 2023 г.В этой статье вы не узнаете, как настроить виртуальную среду и установить FastAPI для создания приложения для работы с задачами. Вместо этого он предложит новую перспективу, если у вас есть опыт использования FastAPI для создания серверных систем. В прошлом вы могли выполнять запросы к базе данных в своем представлении API или модуле бизнес-домена. Однако рекомендуется прекратить эту практику. Шаблон репозитория — лучший вариант, поскольку он способствует написанию более чистого кода и позволяет избежать потенциальных проблем. В этой статье я объясню причины использования этого шаблона проектирования и преимущества, которые он дает.
Введение
Шаблон репозитория — это интерфейс, в котором хранится логика доступа к данным. Проще говоря, прекрасная идея использования шаблона репозитория состоит в том, чтобы отделить уровень доступа к данным (базу данных) от уровня доступа к сервису (домена) приложения. Операции CRUD (создание, чтение, обновление, удаление и т. д.) выполняются с помощью методов, не беспокоясь о соединениях с базой данных, командах и т. д.
Ради этой статьи мы создадим простой сокращатель URL. Полный код проекта вы найдете на моем GitHub; вы должны внимательно следить за тем, насколько невероятно крут RP (шаблон репозитория).
В наших моделях у нас есть следующий код:
# Stdlib Imports
from datetime import datetime
# SQLAlchemy Imports
from sqlalchemy import Column, String, Integer, DateTime
# Own Imports
from config.database import Base, DATABASE_ENGINE
async def create_tables():
Link.metadata.create_all(bind=DATABASE_ENGINE)
class Link(Base):
__tablename__ = "links"
id = Column(Integer, primary_key=True, index=True)
original = Column(String)
shortened = Column(String(4))
date_created = Column(DateTime, default=datetime.now)
date_modified = Column(DateTime, onupdate=datetime.now)
def __str__(cls) -> str:
"""
`__str__` is a special method that returns a string representation of an object.
:param cls: The class object
:return: The shortened version of the original link
"""
return cls.shortened.__str__()
Для читателей, которые плохо знакомы с FastAPI, я быстро расскажу, что делают create_tables
и Link
. Асинхронный метод create_tables
отвечает за создание таблиц базы данных определенного класса ссылок. Мы определяем необходимые поля (столбцы), которые мы хотим иметь в нашей таблице ссылок.
Следующим будет модуль с именем repository.py
, здесь мы собираемся создать интерфейс, в котором хранится наша логика доступа к данным. У нас есть следующий код:
# Stdlib Imports
from typing import List
# SQLAlchemy Imports
from sqlalchemy.orm import Session
# Own Imports
from config.deps import get_db
from shortener.models import Link
class LinkRepository:
"""Repository responsible for performing operations (CRUD, etc) on links table."""
def __init__(self) -> None:
self.db: Session = get_db().__next__()
async def create(self, original: str, shortened: str) -> Link:
"""
This method is responsible for creating a new link object.
:param original: original url link
:param shortened: shortened url link
:return: the link object
"""
link = Link(original=original, shortened=shortened)
self.db.add(link)
self.db.commit()
self.db.refresh(link)
return link
async def get(self, skip: int, end: int) -> List[Link]:
"""
This method retrieves a list of links objects.
:param skip: The number of links to skip
:type skip: int
:param end: The number of links to retrieve
:type end: int
:return: A list of link objects
"""
links = self.db.query(Link).offset(skip).limit(end).all()
return links
async def get_code(self, code: str) -> Link:
"""
This method retrieves a link object that matches the code.
:param code: The shortened code of the link
:type code: str
:return: The link object
"""
link = self.db.query(Link).filter_by(shortened=code).first()
return link
link_repository = LinkRepository()
Чтобы начать использовать наш класс репозитория; нам нужно его инициализировать.
def __init__(self) -> None:
self.db: Session = get_db().__next__()
Цель этого метода — инициализировать экземпляр класса путем создания сеанса базы данных и сохранения его в переменной экземпляра self.db
. При этом сеанс базы данных будет создан и сохранен в экземпляре класса при создании экземпляра класса и будет доступен для использования в течение всего времени существования экземпляра.
get_db
— это функция, которая отвечает за создание сеанса базы данных, его выход и откат транзакции. Если во время работы с базой данных возникает исключение, функция перехватывает исключение и вызывает db .rollback()
для отката транзакции. Это гарантирует, что любые изменения, внесенные в базу данных во время транзакции, будут отменены в случае ошибки; и наконец
блок функции закроет сеанс, используя метод db.close()
, независимо от того, было ли возбуждено исключение или нет.
Код этой функции выглядит следующим образом:
# Own Imports
from config.database import SessionLocal
def get_db():
"""
This function creates a database session,
yield it to the get_db function, rollback the transaction
if there's an exception and then finally closes the session.
Yields:
db: scoped database session
"""
db = SessionLocal()
try:
yield db
except Exception:
db.rollback()
finally:
db.close()
Метод create
отвечает за создание нового объекта Link
с заданной исходной ссылкой URL и сокращенным кодом. После чего сохраняет его в переменной link
. Затем метод добавляет ссылку в базу данных, вызывая self.db.add(link)
.
Затем метод фиксирует изменения в базе данных, вызывая self.db.commit()
. Затем вызывается метод refresh
с объектом link
в качестве аргумента для обновления состояния ссылки в базе данных. Наконец, метод возвращает вызывающей стороне объект link
.
Метод get
отвечает за получение списка объектов Link
из базы данных. Метод создает запрос для извлечения объектов Link
из базы данных с помощью оператора self.db.query(Link)
. Затем в запросе вызывается метод offset
, чтобы указать количество ссылок, которые нужно пропустить, и метод limit
, чтобы указать количество ссылок, которые необходимо получить. Наконец, в запросе вызывается метод all
для получения всех ссылок, соответствующих критериям.
get_code
отвечает за получение одного объекта Link
. Метод создает запрос для извлечения объекта Link
из базы данных с помощью оператора self.db.query(Link)
. Затем в запросе вызывается метод filter_by
, чтобы указать критерии получения ссылки, в данном случае сокращенного кода. Наконец, в запросе вызывается метод first
для получения первой ссылки, соответствующей критериям.
Таким образом, методы обеспечивают удобный и асинхронный способ выполнения определенных действий в базе данных, будь то; создание и сохранение новых объектов Link
в базе данных, или получение списка объектов Link
из базы данных, или получение одного объекта Link
во время убедиться, что сеанс базы данных правильно управляется и транзакция фиксируется, если нет исключений.
Теперь, когда вы понимаете, что происходит, давайте перейдем к уровню доступа к нашим сервисам; здесь хранится логика домена. У нас есть следующий код:
# Stdlib Imports
import random
import string
# FastAPI Imports
from fastapi.responses import RedirectResponse
# Own Imports
from shortener.repository import link_repository, Link
async def shorten_link() -> str:
"""
This function returns a random string of 4 characters.
:return: A string of 4 random letters.
"""
shrt_str = "".join(random.choice(string.ascii_letters) for i in range(4))
return shrt_str
async def create_shortened_link(original: str) -> Link:
"""
This function creates a shortened link for the given original link.
:param original: str - the original link that we want to shorten
:type original: str
:return: A Link object
"""
shortened_link = await shorten_link()
link = await link_repository.create(original, shortened_link)
return link
async def redirect_to_original_link(code: str) -> str:
"""
This function takes a code and returns the original link.
:param code: str - the code that was generated by the shortener
:type code: str
:return: redirect to original link
"""
link = await link_repository.get_code(code)
return RedirectResponse(link.original)
Если вы посмотрите на функции create_shortened_link
и redirect_to_original_link
, мы получим доступ к методам create
и get_code
в нашем интерфейс репозитория. Мы отказались от написания запросов к базе данных на нашем сервисном уровне, что сделало наш код чистым и быстрым для отладки.
Преимущества использования шаблона репозитория
Использование шаблонов проектирования репозитория дает несколько преимуществ, в том числе:
- Облегчение тестирования логики вашего приложения
- Уменьшение количества повторяющихся операций с базой данных
- Централизованное управление уровнем базы данных, позволяющее легко внедрять политики доступа
- Возможность определять надежные аннотации для объектов домена.
Заключительные мысли
В этой статье мы объяснили преимущества использования шаблона проектирования репозитория при разработке программного обеспечения. Шаблон репозитория отделяет уровень доступа к данным от уровня доступа к службе в приложении. Это приводит к более чистой и организованной кодовой базе, а также уменьшает количество дублирующихся операций с базой данных.
Мы использовали простой сокращатель URL-адресов в качестве примера, чтобы проиллюстрировать реализацию шаблона репозитория. Код демонстрирует, как создать таблицу базы данных и как сохранить логику доступа к данным в классе репозитория. Полный код этого примера можно найти здесь.
Оригинал