Как стать автором
Обновить

Комментарии 9

Спасибо за проделанную работу.

Подобных, подробных и практических, статей не встречал. Хотя искал много.

Советую перевести на английский и собирать трафик.

Спасибо большое за ваш комментарий! А то было совсем грустно, что статья не нашла отклика. Но если она пригодилась хотя бы одному читателю, значит время потрачено не зря!

Подобных, подробных и практических, статей не встречал. Хотя искал много.

Возьмите эту книгу 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 реализует этот интерфейс.

Я про второй случай. Вы берёте Protocol, но используете его как ABC. Structural subtyping не про это. В PEP 544 есть примеры с явным наследованием и вот что там об этом написано:
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 - нужно убрать, оно тут совсем не к месту :)

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.