В недавнем проекте мне пришлось добавить бизнес-представление (и концептуальное объектно-ориентированное) поверх необработанной схемы SQL (которая не может быть изменена по причинам устаревшего характера; она исходит из широко распространенного инструмента с открытым исходным кодом). Этот «бизнес-уровень» также должен реализовать некоторую тривиальную логику, например:
- Пользователя нельзя создать, если он уже существует
- Пользователя нельзя добавить в несуществующую группу
- Группу нельзя удалить, если она все еще содержит пользователи
- удаление пользователя приведет к каскадному удалению некоторых связанных с пользователем элементов
Так я и закончил. наличие UserRepository:
Код: Выделить всё
# User model (aggregate root)
class User:
def __init__(self, name: str, groups: List[Group] = [], items: List[Item] = []):
# some invariant
if not (groups or items):
raise ValueError("A user must have at least one item or must belong to at least one group")
self.name = name
self.groups = groups
self.items = items
# User repository
class UserRepository:
def __init__(self, db_conn):
self.db_conn = db_conn
def exists(self, username: str) -> bool: # ...
def find_all(self) -> list[User]: # ...
def find_one(self, username: str) -> User:
if not self.exists(username):
return None
groups = self.db_conn.execute(f"SELECT FROM usergroup WHERE ...")
items = self.db_conn.execute(f"SELECT FROM useritem WHERE ...")
return User(username, groups, items)
def add(self, user: User):
for group in user.groups:
self.db_conn.execute(f"INSERT INTO usergroup ...") # insert group belongings
for item in user.items:
self.db_conn.execute(f"INSERT INTO useritem ...") # insert user items
def remove(self, username: str):
self.db_conn.execute(f"DELETE FROM usergroup WHERE ...") # delete group belongings
self.db_conn.execute(f"DELETE FROM useritem WHERE ...") # delete items
А что насчет пользовательской логики? Согласно DDD, он находится в UserService:
Код: Выделить всё
class UserAlreadyExistsException(Exception): # ...
class UserNotFoundException(Exception): # ...
class GroupNotFoundException(Exception): # ...
class UserService:
def __init__(self, user_repo: UserRepository, group_repo: GroupRepository):
self.user_repo = user_repo
self.group_repo = group_repo
def get(self, username: str) -> User:
user = self.user_repo.find_one(username)
if not user:
raise UserNotFoundException
return user
def create(self, user: User) -> User:
if self.user_repo.exists(user.name):
raise UserAlreadyExistsException
for group in user.groups:
if not self.group_repo.exists(group.name):
raise GroupNotFoundException(f"'{group.name}' has not been found")
self.user_repo.add(user)
return user
def delete(self, username: str):
if not self.user_repo.exists(username):
raise UserNotFoundException
self.user_repo.remove(username)
У меня есть два приложения, которые, в свою очередь, используют эту службу: веб-приложение (REST API) и приложение Python. Они будут использовать эту службу и доверять ей, чтобы обеспечить соблюдение бизнес-правил (не дублировать пользователей, не создавать пользователей в несуществующих группах и т. д.).
Код: Выделить всё
# Web app consuming UserService
@router.get("/users/{username}", status_code=200)
def get_user(username: str) -> User:
try:
return user_service.get(username)
except UserNotFoundException:
raise HTTPException(404, "Given user does not exist")
@router.post("/users", status_code=201)
def post_user(user: User) -> User:
try:
return user_service.create(user)
except UserAlreadyExistsException:
raise HTTPException(409, "Given user already exists")
except GroupNotFoundException as exc:
raise HTTPException(422, str(exc))
@router.delete("/users/{username}", status_code=204)
def delete_user(username: str):
try:
user_service.delete(username)
except UserNotFoundException:
raise HTTPException(404, detail="Given user does not exist")
Глядя на этот популярный пример проекта DDD на Java, мы видим ту же проблему: CustomerService выдает исключение, которое перехватывается и повторно вызывается в связанном веб-сервисе. Кто-то спрашивал об этом, но не получил ответа.
Альтернативой была бы повторная реализация логики в моих приложениях, но это привело бы к множеству проблем:
- UserService должен будет реплицировать операции UserRepository, чтобы сделать их доступными
- логика предметной области будет просачиваться в уровень приложения
- логика предметной области будет дублироваться в каждое приложение
В этой статье 2023 года также используется (и предлагается) поток управления на основе исключений:
Код: Выделить всё
class PaymentController {
public Response handlePayment(Request request, int amountInCents) {
try {
this.paymentService.createPaymentForUser(request.getUser(), amountInCents);
return new Response(200, new HashMap());
} catch (PaymentValidationException ex) {
throw new ResponseStatusException(400, ex.getMessage())
} catch (UserNotFoundException ex) {
throw new ResponseStatusException(401, "You are not authorized to create payments for this user");
}
}
}
Подробнее здесь: https://stackoverflow.com/questions/790 ... ry-pattern