Комментарии 10
Спасибо за проделанную работу.
Подобных, подробных и практических, статей не встречал. Хотя искал много.
Советую перевести на английский и собирать трафик.
Спасибо большое за ваш комментарий! А то было совсем грустно, что статья не нашла отклика. Но если она пригодилась хотя бы одному читателю, значит время потрачено не зря!
Подобных, подробных и практических, статей не встречал. Хотя искал много.
Возьмите эту книгу www.amazon.com/Architecture-Patterns-Python-Domain-Driven-Microservices/dp/1492052205 она о том же, но более подробно, с большим количеством примеров.
Секта свидетелей чистой архитектуры :)
Спасибо pantafive, который указал на ошибку в дизайне с точки зрения DDD:
VotingUser
является агрегатом, поэтому он должен инкапсулироватьArticleVote
и репозиторий должен быть только у него. При вызове методаrepository.save()
должен обновляться (при необходимости) пользователей и добавляться новый голос.
Таким образом VotingUser.vote(...)
лишь изменит своё внутренее состояние. Ещё лучше — выделить "Голосующего Пользователя" и "Проголосовашего Пользователя" в разные модели. Тогда метод VotingUser.vote()
вернёт проголосовавшего пользователя у которого можно "спросить", как он проголосовал:
class VotingUser:
def vote(...) -> VotedUser:
...
class VotedUser:
@property
def article_vote(self) -> ArticleVote:
...
Вы имеете ввиду наследование от Protocol
, например в
class CastArticleVoteUseCase(Protocol):
def cast_article_vote(self, command: CastArticleVoteCommand) -> CastArticleVoteResult:
raise NotImplementedError()
или наследование в сервисах/адаптерах, например
class ArticleVoteRepository(
SaveArticleVotePort,
):
?
В первом случае - явным образом объявляется интерфейс. Технически можно было бы использовать и абстрактный класс и метод, но протоко мне показался более уместным.
Во втором случае - безусловно можно было бы обойтись без "наследования" и статический анализатор даже бы не заикнулся на чём-то вроде save_article_vote_port: SaveArticleVotePort = ArticleVoteRepository()
. Но здесь и зарыта собака - т.к. этот протокол - "голый" интерфейс, нас не интересует наследование. Мы хотим явным образом показать в коде, что ArticleVoteRepository
реализует этот интерфейс.
To explicitly declare that a certain class implements a given protocol, it can be used as a regular base class. In this case a class could use default implementations of protocol members. Static analysis tools are expected to automatically detect that a class implements a given protocol. So while it's possible to subclass a protocol explicitly, it's not necessary to do so for the sake of type-checking.
Делать
raise NotImplementedError()
в теле метода протокола, судя по PEP 544, тоже не нужно, достаточно либо написать докстринг, либо ограничиться ellipsis или pass.Я лишь хотел донести что вы взяли Protocol, который про structural subtyping и используете его как абстрактный класс, а для этого в стандартной библиотеке Python уже есть модуль abc, который и следовало бы использовать.
Полностью с вами согласен по всем пунктам. Использование Protocol
в данном случае вообще не о structural subtyping и это вводит в заблуждение. Здесь больше моя интерпретация и её ноги растут скорее из классической Java (до версии 8).
Подсознательно я смотрю на абстрактный класс как на что-то тяжелое, несущее в себе базовый код и оставляющий пару абстрактных методов и свойств для имплементации при наследовании.
Protocol же я рассматриваю как чистый интерфейс, в котором есть лишь абстрактные методы. NotImplementedError
- нужно убрать, оно тут совсем не к месту :)
Architecture is about intent
Вообще не понял сути этой части публикации.
Вы сравниваете архитектуру инструмента с архитектурой здания, в которой, внезапно, уже расположены какие-то индивидуальные особенности проекта. В таком случае надо было показывать не эту схему, а цемент, шпатель, фотографии рабочих и т.п.
И тогда было бы примерно равное соотношение.
UPD
А, статья 21 года. Что-ж...
Domain-driven design, Hexagonal architecture of ports and adapters, Dependency injection и Python