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

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

ЗакрепленныеЗакреплённые комментарии

Если отложить в сторону фантазии Эванса, а опираться только на периодические публикации, то может показаться, что DDD - это Directory Driven Design, вследствие того, что описание подхода зачастую начинается и заканчивается структурой директорий в проекте.

В Go, избегая циклической зависимости, либо неуместно чрезмерной инверсии зависимостей, так или иначе, приходится выстраивать слои, где зависимости импортируются снизу вверх. Получившуюся структуру можно называть чистой, луковой, DDD или жареной курицей - не имеет значения, она выстраивается естественным путем.

На мой взгляд, парадокс методик заключается в том, что большинство из них интуитивны и просты, но их описание сложно и запутывает тех, кому предстоит учиться. Тем же, кто "всю жизнь говорил прозой" и не подозревал об этом, такого рода книги уже бесполезны. Паттерны GoF, например, если читать рано, то не ясно как это все применять, а если читать позже, то только ради того, чтобы узнать, что глобальный экземпляр называют синглтон.

"Они запускали в космос маленькие серебряные шарики с усиками. Я называю их - спутник."

Вопрос - а в func (r AndRule) IsDepositAllowed

Вам действительно нужна копия AndRule? Почему не ссылка?

То же самое для всех остальных методов

Это всего лишь доступный в Go способ явно обозначить, что данный метод не меняет состояние объекта. Разумеется, в тех случаях, когда это может создать проблемы с производительностью, так делать не стоит.

Так оно по ссылке будет медленней же в этом кейсе. Потому что при вызове метода вам нужно будет брать ссылку на структуру, вместо того, чтобы скопировать сторуктру размером 0

и вот это мне и не нравится в Go, т.к. по сути, неизменяемость объекта выражается не через механизм языка, а через костыль.

Через 5 лет структура получит пару полей, а про метод забудут - получим проблему на ровном месте

да и вообще вы не правы AndRule имеет размер не ноль

type AndRule struct {

left AntifraudRule
right AntifraudRule

}

А, верно. Перепутал с пустой структурой, которая реализует этот интерфейс

Но, по-моему, даже с таким размером структуры будет немного быстрее использовать ресивер без поинтера, особенно если в будущем будут использоваться члены структуры. Нужно будет проверить, давно делал сравнение :)

Передача объекта по ссылке происходит с использованием кучи(heap память), в то время как копирование происходит через stack. Через стэк чаще всего ощутимо быстрее, но есть разные тонкости и нюансы. Надо бенчмаркать каждый конкретный случай.

Передача объекта по ссылке происходит с использованием кучи

Не всегда ссылка означает кучу :) это решает escape analysis

Мне кажется, что почти все гоферы когда-то мигрировали в мир Go из других миров. В основном это миры хардкорного ООП.

Хочу сказать, что он делает действительно классные вещи, пишет безупречный код, у него есть, чему поучиться.

Этот мой приятель, не побоюсь этого слова — адепт DDD (domain driven design).

Возможно гоферы как раз таки и мигрировали подальше от приятелей которые адепты с безупречным ДДД кодом?

НЛО прилетело и опубликовало эту надпись здесь

Чисто для дискуссии.
А чем ДДД так не угодил? В чистом виде, как и любой ‘чистый’ фреймворк, конечно избыточен, но мысли-то все верные, нет?

да в общем то всем угодил, как и любой инструмент использующийся по назначению, акцент на безупречных адептах был)

Своей вот избыточностью и не угодил. Адепты при этом игнорируют любые аргументы универсальной фразой "ну да, не везде подходит, не всегда, нужно нормально делать и нормально будет". И продолжают использовать (пытаться) всегда и везде.

Если отложить в сторону фантазии Эванса, а опираться только на периодические публикации, то может показаться, что DDD - это Directory Driven Design, вследствие того, что описание подхода зачастую начинается и заканчивается структурой директорий в проекте.

В Go, избегая циклической зависимости, либо неуместно чрезмерной инверсии зависимостей, так или иначе, приходится выстраивать слои, где зависимости импортируются снизу вверх. Получившуюся структуру можно называть чистой, луковой, DDD или жареной курицей - не имеет значения, она выстраивается естественным путем.

На мой взгляд, парадокс методик заключается в том, что большинство из них интуитивны и просты, но их описание сложно и запутывает тех, кому предстоит учиться. Тем же, кто "всю жизнь говорил прозой" и не подозревал об этом, такого рода книги уже бесполезны. Паттерны GoF, например, если читать рано, то не ясно как это все применять, а если читать позже, то только ради того, чтобы узнать, что глобальный экземпляр называют синглтон.

"Они запускали в космос маленькие серебряные шарики с усиками. Я называю их - спутник."

Плюсую!

Ваш приятель не противопостояляет хорошее DDD плохому Go. Ваш приятель противоставляет образ себя хорошего враждебному и сложному миру с целью произвести впечатление и сойти за умного.

Например возьмем его слова: Go - это *****код. В Go криво реализована обработка ошибок. Все, что не относится к DDD - антипаттерн, ад и хаос.

И выделим паттерн: X - это говно, потому что гладиолус. Все, что не Y - по умолчанию плохо(а значит Y хорошо и это не нуждается в доказательствах).

X = все, что угодно чужое.

Y = любая херня относящаяся к автору фразы.

Потому что гладиолус = потому, что мне так нужно. Это хитрое подобие логики, где аргумент не обязательно связан с предметом, хотя может быть на это похоже. Случайный факт, заменяющий явный личный интерес.

Теперь пробуем на практике:

  • iPhone - это говно. В iPhone нельзя свернуть вызов. Все, что не Android - антипаттерн, ад и хаос.

  • Android - это говно. В Android нельзя дефрагментировать. Все, что не Android - антипаттерн, ад и хаос.

  • Статья автора - это говно. В статья автора нет ссылки на репозиторий и презентации. Все, что без презентации - антипаттерн, ад и хаос.

  • DDD - это говно. В DDD нельзя описывать бизнес логику в сервисах. Все, что не Model Driven Architecture - антипаттерн, ад и хаос.

Рассуждение о преимуществах и недостатках DDD как такового выходит за рамки данной публикации, но возможно я напишу свои мысли на этот счёт в будущем. Спасибо за идею :)

давай!

В этом богоугодном деле главное не стать похожим на очередного джуна, который прячет свой страх «неправильно» разложить файлы в проекте, за разговорами о паттернах :)

Не стоит благодарить за идею, которую вам не предлагали.

Прямо как у классика :)

Четыре иллюстрации того, как новая идея огорашивает человека, к ней не подготовленного

1. Писатель: Я писатель.
Читатель: А, по-моему, ты говно!
(Писатель стоит несколько минут, потрясенный этой новой идеей, и падает замертво. Его выносят.)

2. Художник: Я художник!
Рабочий : А, по-моему, ты говно!
(Художник тут же побледнел как полотно, и как тростинка закачался и неожиданно скончался. Его выносят.)

3. Композитор: Я композитор!
Ваня Рублев: А, по-моему, ты говно!
(Композитор, тяжело дыша, так и осел. Его неожиданно выно-
сят.)

4. Химик: Я химик!
Физик: А, по-моему, ты говно!
(Химик не сказал больше ни слова и тяжело рухнул на пол.)

(с) Д.Хармс

Красиво описано по существу) прям в учебник можно

Возвращать интерфейс - не самая лучшая идея в go. Немного похоже на интерфейсы ради интерфейсов.

Заодно начисто убивает идею и преимущества duck typing.

Как я и указал в статье, местами приходится отступать от различных go way гайдов. Язык - это всего лишь инструмент. :)

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

Ну иметь сложности с циклическими зависимостями, плодит пакеты, не иметь duck typing, иметь нужду и импортировать пакеты только ради интерфейсов, и прихватить overhead заодно - это понятно. Но зачем? Чем не устраивает обычное разделение на публичные и приватные методы и поля? В чем жизненая необходимость вводить интерфейсы? Какую они задачу решают?

Этот вопрос видимо можно перефразировать в “Какую задачу решает DDD?” Это довольно обширная тема, пожалуй потянет для отдельной статьи

Отнюдь. Убираем ненужные интерфейсы и DDD никуда не делось.

Не стоит переделывать мой вопрос.

Окей. Если вы про интерфейсы репозиториев, то основная идея - сделать так, чтобы у домена не было зависимостей. Кроме того, в домене не должно быть деталей реализации запросов к БД и прочего.

Я описал в статье один из способов как изолировать разные слои друг от друга. Мне понравился способ максимальной изоляции через полное скрытие конкретных реализаций, и взимодействие слоев только через интерфейсы. Если вы желаете разложить файлики в папочках по-другому, никто и не против. :)

Если отойти от DDD, то такой вам вопрос: юнит-тесты в Go пишете?

Для тестов вам надо только принимать параметры по интерфейсу, но не возвращать.

Про изоляцию. У вас конструкторы возвращают интерфейсы вместо самих объектов. То есть вы не убрали, а добавили пакет в зависимости.

Окей, мы получается вернулись к первому вопросу, который не про избыточность интерфейсов как таковых, а про конкретный пример с возвращением интерфейса из конструктора.

Мне пришлось это сделать именно для реализации примера с идеей выноса всех реализаций в internal. Эта идея не претендует на то, чтобы быть единственно верной, и, как и у любого другого подхода, есть свои преимущества и недостатки.

Отнюдь. Вопрос про избыточность не снят, поскольку они не решают никакой задачи, которая не может быть решена без их применения. И без возвращения интерфейсов всё будет работать и обладать теми же свойствами.

Главный вопрос к такому подходу в том, что это применение неподходящих инструментов. Это приводит к мысли, что изначальная проблема была не в го, а в конкретном подходе к реализации DDD. Где так и неясно, для чего же надо вводить дополнительные абстракции, если и инкапсуляцию без них можно достичь и разделение слоёв и типов по ответственности.

Вы отрицаете необходимость интерфейсов в принципе?

Я не согласен, но мне бы получше узнать вашу точку зрения.

Это не тот вопрос, который я задал ;)

Я написал, что если без введения интерфейса можно решить задачу и иметь все нужные свойства, то интерфейс добавлять в golang не надо. И мне неясна необходимость в большой части интерфейсов в ваших примерах.

Лично я бы и пустые структуры убрал в пользу более функционального подхода. Но вот это уже точно вкусовщина.

Отойдем от DDD. Юнит-тесты пишете в Go? Как мокаете зависимости?)

«Я написал, что если без введения интерфейса можно решить задачу и иметь все нужные свойства, то интерфейс добавлять в golang не надо.» Вроде должно отвечать на повторяемый вами вопрос. И да, не моками едиными ) Можно и в функциональный подход переписать и передавать свои функции-заглушки. Просто как вариант, чтобы показать, что указанный в статье подход не является единственным решением для тестирования, если кто-то пытается это использовать как аргумент. Хотя по сути аргумент один «так захотелось».

NewAntifraud - как его тестировать, есть зависимости создаются напрямую в конструкторе? То есть вся предыдущая пляска с интерфейсами была не нужна?

MoonRule - не имеет зависимостей, равно не имеет полей и ничего изолировать вообще не надо.

По вашему коду имплементация и интерфейс лежат на одном уровне: MoonRule-AntifraudRule; AndRule-AntifraudRule; и там же интерфейсы User, Wallet, Money. При таком подходе смысл в интерфейсах не такой большой, поскольку всё лежит в одном большом пакете. Если же разбивать на пакеты то как? Выносить интерфейсы на уровень выше? Но тогда имплементация будет зависеть от уровня выше. Оставлять на том же уровне? Тогда не разрывается зависимость от пакета с имплементацией. Добавлять новый пакет в пакет с имплементацией? Только так останется поступить. Но опять не решенным остается вопрос конструкторов. У вас интерфейсы не уменьшают связность кода, что как-то странно. Должно быть наоборот.


Ну вот вы тестируете что-то, что зависит от репозитория. Вам удобно будет замокать репозиторий. Мне не до конца понятно, зачем отказываться от чего-то, что удобно. :)

Про antifraud: в этом примере я показал сам паттерн, а не разбивку на пакеты. Вы можете разбивать на пакеты так, как считаете нужным.

Кажется, мы с вами все-таки говорим о разных вещах. По-моему, вы просто триггернулись но одно несоответствие стайлгайдам. :)

Как-то вы плохо приписываете мне то, чего я не писал. Попробуйте строго пройти по тексту, который выше. Там нет ничего о том, что интерфейсы не нужны. Там о том, что в го они не нужны так, как вы их используете. И обоснования почему: консирукторы с дополнительной зависимостью на интерфейс, который конструируемый тип имплементирует; большая связность кода; убийство фичи duck typing; отсутствие возможности в вашем же коде прокинуть зависимости; и то, что юниты можно не только на моках писать, часто можно обойтить передачей функции. Вы ведь понимаете, что можно DDD и в функциональном подходе иметь? И тестировать не только через тестовые имплементации интерфейса?

И да. Вы так и не ответили на вопрос о том, что у нас любой пакет, который зависит от инплементации заодно зависит и от интерфейса. Что есть прямая дорога к циклическим зависимостям. Да и то, что имплементации зависят от уровня выше не смотрится чисто.

Я пытаюсь показать, что у вас реализация не DDD, а стайлгайда на DDD, притащенного из других языков. И ваш стайлгайд DDD в го работает плохо.

> Вы ведь понимаете, что можно DDD и в функциональном подходе иметь?

Мое мнение: каждый подход имеет право на существование, и у каждого подхода есть плюсы и минусы. Думаю, я понял вашу мысль, вы топите за функциональное программирование. И это просто отлично, без шуток. Я бы все-таки от вас хотел бы увидеть публикации на Хабре на эту тему. Наверняка у вас есть интересный опыт, которым вы могли бы поделиться, какие-то хорошие идеи, которые читатели могли бы взять на вооружение.

Нет, не топлю за функциональное. Вы интересно читаете собеседника: "Можно и в функциональный подход переписать и передавать свои функции-заглушки. Просто как вариант, чтобы показать, что указанный в статье подход не является единственным решением для тестирования, если кто-то пытается это использовать как аргумент."

Но мои вопросы вы "не заметили". Так что с теми проблемами, которые добавляют неуместные интерфейсы в вашем коде?

Итак,
- User, Wallet и Amount в моем примере интерфейсами не являются.
- Интерфейсы для репозиториев - ок, можем без них обойтись. Однако, на мой вкус, лучше бы их оставить объектами, а не переписывать в ФП (но тут кому как) и использовать для них моки. Кроме того, источники данных, к которым фактически будут обращаться репозитории, могут меняться, и тот, кто от них зависит, лучше чтобы не знал об этом факте. Но ок, предположим, такого никогда не будет, и мы точно об этом знаем. Ноу проблем, интерфейсы убираем.
- Интерфейсы в паттерне "спецификации". Вот тут как раз-таки вся соль в том, что конкретная комбинация правил сильно зависит от конкретного случая применения приложения, очень часто вносятся изменения в эту комбинацию. Весь остальной код, обращающийся к антифроду, не должен иметь ни малейшего понятия, с какой комбинацией правил он имеет дело в данный конкретный момент. Он должен знать только, что у него есть сейчас в распоряжении правильно настроенный/собранный объект, и, когда ему нужно, он ему всегда может задать вопрос и получить ответ.

Да, решая одни проблемы, мы можем создать другие, это всегда компромисс.

И что поменяется такого страшного, если у вас конструкторы начнут возвращать объекты? Хотите - возвращайте приватные объекты. Да и обычные публичные ни инкапсуляцию, ни разделения слоёв не допустят. Или гайдлайн говорит, что так нельзя?)

Кстати, там выше у вас есть странные штуки, когда есть неиспользуемый ресивер по значению, чтобы "обозначить только для чтения). Лучше не так делать:

```

func (MoonRule) IsDepositAllowed(user User, wallet Wallet, amount Money) (bool, error) {

if IsRisingMoon() {

return true, nil

}

return false, nil

```

Про классику в виде "источники данных могут и меняться". У вас часто на проде базы меняются?) Обычно это очень больно и происходит примерно никогда.

А если например конкретная комбинация зависит от конфига? Что возвращать-то?

Почему есть метод save, но нет метода load? Get тут немного не стыкуется.

Как делаете создание агрегатов и их сохранение. Так то с простыми примерами все понятно.)

Про агрегаты - интересный вопрос. Врать не буду: в Go мы пока старались без них обходиться. :) Но есть пара идей, и думаю скоро провести серию экспериментов. По результатам напишу)

Зарегистрируйтесь на Хабре, чтобы оставить комментарий