На сколько я знаю для транзакций есть отдельные механизмы в каждой ORM В нашем примере можно сделать так: const session = await mongoose.startSession(); session.startTransaction(); try { await Lesson.create([{ name: 'test' }], { session }); await Course.create([{ name: 'test' }], { session }); await session.commitTransaction(); } catch (e) { await session.abortTransaction(); throw e; } finally { session.endSession(); }
В нашем реализации используется mongoDB. lesson это поле документа course, так что при удалении course lesson удалится каскадом
Опять же в нашей реализации с mongo мы можем получить как весь документ course, так и отдельные его поля и под-документы lesson. В других ORM вроде тоже есть механизмы select и lazy select которые тянут либо сущность, либо сущность и ее связи
Если рассматривать чисто как контракт взаимодействия слоев, то конечно лучше интерфейсы. Их и мокать проще для тестов и множественное наследование работает. Но если есть какая либо логика реализацию которой можно вынести, то тут уже помогут только абстрактные классы.
Механизмов выпуска событий пока что не делали
http был выбран для примера. Обычно присутствует весь зоопарк, и graphQL и брокеры и секеты и тд
"Пропихнуть" экземпляр вместо интерфейса нам ничего не мешает. Так же как нам ничего не мешает вообще не делать интерфейсов, слоев и абстракций. Это все условные правила которых мы придерживаемся что бы добиться "простоты" использования, расширяемости, сопровождения и тд.
Тут уже фундаментальное поле для дискуссий — оправдано ли существование вообще хоть какой либо архитектуры.
Вот тут не понял. Если у вас этот слой использует различные интерфейсы из инфраструктурного слоя
В инфраструктурном слое нет интерфейсов. Интерфейс репозитория это слой Domain или Application. Сами репозитории лишь реализуют интерфейсы из слоев выше и => зависят от них.
Ясно указывает, что слой приложения зависит и от слоя представления
Прямой связи нет. Все через абстрактные классы.
Да, благодаря внедрению зависимостей и абстрактным интерфейсам разорвана связь между реализацией, но сама связь сохранена.
Разорвана. Не могу понять где вы видите ее сохранение.
Причем вы даже ниже сами же используете такую же фигуру речи: "Таким образом, когда в Application-слое в конструкторе LessonService мы зависим от VideoLessonRepository (абстракции), NestJS внедрит нам экземпляр VideoLessonRepositoryImpl из инфраструктуры. "
Не вижу противоречий. Взяли абстракцию, ничего не знаем о реализации, в нужном месте указали реализацию.
Или вы хотите сказать, что абстрактные классы/интерфейсы слоев представления, инфраструктуры не относятся к этим слоям? Но тогда слой приложения зависит не только от слоя доменов, но еще и от слоя, который обслуживает вот эти интерфейсы.
Именно. "от слоя, который обслуживает вот эти интерфейсы" - т.е. от самих себя? Да, так и есть.
Такая же фигня. Я совершенно запутался кто и от кого зависит. Каким образом инфраструктура зависит от приложения?
Тем что реализует абстракции из него.
В вашем примере как раз все наоборот инфраструктура зависит от абстрактного интерфейса инфраструктуры, от которого зависит уровень приложения. При этом инфраструктура так же зависит и от Домена.
Если как вы посчитали " инфраструктура зависит от абстрактного интерфейса инфраструктуры", то где вы увидели что "При этом инфраструктура так же зависит и от Домена"?
У вас другие зависимости. Как минимум в показанных примерах.
Все еще не могу понять где вы это увидели?
вашем примере Application зависит от Infrastructure
Вы путаете зависимость от абстрактных классов Infrastructure и от самого Infrastructure.
Все таки ознакомьтесь дополнительно с "принципом инверсии зависимостей"
Возможно я не понял изначально вопроса, прошу прощения.
Мы убирали связь реализации инфраструктуры от бизнес логики, но не связь как таковую вообще.
"Пропихнуть" экземпляр вместо интерфейса нам ничего не мешает. Так же как нам ничего не мешает вообще не делать интерфейсов, слоев и абстракций. Это все условные правила которых мы придерживаемся что бы добиться "простоты" использования, расширяемости, сопровождения и тд.
Тут уже фундаментальное поле для дискуссий — оправдано ли существование вообще хоть какой либо архитектуры.
Ознакомьтесь пожалуйста с "принципом инверсии зависимостей". Он даст вам понять почему зависимость от интерфейса != зависимости от реализации интерфейса и поможет разобраться в примерах.
В DDD для этого рекомендуют делать и выносить отдельные мапперы, как мы и сделали в итоге: маппер из схемы БД в домен и обратно, и маппер с dto в доменный объект и обратно
декораторы для валидации звучит всрато, это у вас в js такое изобрели?
Если для сборки доменной сущности нужно много join'ов — это нормально, и это допустимо внутри именно репозитория, потому что задача репозитория — восстановить агрегат из хранилища.
Главное правило: обо всей этой сложности домен знать не должен, а сложность инфраструктуры не должна лезть в домен.
Как база архитектуры вообще — Роберт Мартин "Чистая архитектура"
А если по DDD то, наверное, Вон Вернон "Domain-Driven Design Distilled" или Влад Хононов "Learning Domain-Driven Design: Aligning Software Architecture and Business Strategy"
Насчет Interface, интересное замечание. В нашем случае у нас там сложены все dto для валидации входных данных с внешнего мира, т.е. с rest хендлеров (в nest.js это классы с стандартными декораторами полей из class-validator). Т.к их достаточно много, выглядело удобно выделить в отдельный слой.
А что касается NestJS, да он действительно вдохновлён Spring и использует похожие подходы: DI-контейнер, декораторы, модули. Но это не обязательно минус - для некоторых проектов это даёт хорошую масштабируемость и структурированность. "ад зависимостей" — хаос можно устроить в любом проекте и на любой платформе. В этом мире все стремится к энтропии))
Если взять легаси в 1000 строк, созданный за 7 человеко-дней, то переход на архитектуру с слоями и DDD-Lite мог занять порядка 10-12 человеко-дней. При этом мы смогли сохранить примерно 50–60% существующего кода, который уже отвечал базовым требованиям качества. Скорость внедрения новых фич увеличилась примерно на 20–25%.
Считали как Если раньше на новую функциональность уходило бы примерно 5 дней (), а после изменений — 4 дня (). Тогда получается что:
Учитывая вариации в оценках и специфику задач, мы наблюдали увеличение скорости внедрения новых фич в диапазоне 25–30%. Эти цифры подтверждаются сокращением времени от идеи до выпуска, ну и самое главное, снижением количества ошибок и скорости погружения в проблематику задачи.
На сколько я знаю для транзакций есть отдельные механизмы в каждой ORM
В нашем примере можно сделать так:
const session = await mongoose.startSession();
session.startTransaction();
try {
await Lesson.create([{ name: 'test' }], { session });
await Course.create([{ name: 'test' }], { session });
await session.commitTransaction();
} catch (e) {
await session.abortTransaction();
throw e;
} finally {
session.endSession();
}
В нашем реализации используется mongoDB. lesson это поле документа course, так что при удалении course lesson удалится каскадом
Опять же в нашей реализации с mongo мы можем получить как весь документ course, так и отдельные его поля и под-документы lesson. В других ORM вроде тоже есть механизмы select и lazy select которые тянут либо сущность, либо сущность и ее связи
Если рассматривать чисто как контракт взаимодействия слоев, то конечно лучше интерфейсы. Их и мокать проще для тестов и множественное наследование работает. Но если есть какая либо логика реализацию которой можно вынести, то тут уже помогут только абстрактные классы.
Механизмов выпуска событий пока что не делали
http был выбран для примера. Обычно присутствует весь зоопарк, и graphQL и брокеры и секеты и тд
Как я писал выше
В инфраструктурном слое нет интерфейсов. Интерфейс репозитория это слой Domain или Application. Сами репозитории лишь реализуют интерфейсы из слоев выше и => зависят от них.
Прямой связи нет. Все через абстрактные классы.
Разорвана. Не могу понять где вы видите ее сохранение.
Не вижу противоречий. Взяли абстракцию, ничего не знаем о реализации, в нужном месте указали реализацию.
Именно. "от слоя, который обслуживает вот эти интерфейсы" - т.е. от самих себя? Да, так и есть.
Тем что реализует абстракции из него.
Если как вы посчитали " инфраструктура зависит от абстрактного интерфейса инфраструктуры", то где вы увидели что "При этом инфраструктура так же зависит и от Домена"?
Все еще не могу понять где вы это увидели?
Вы путаете зависимость от абстрактных классов Infrastructure и от самого Infrastructure.
Все таки ознакомьтесь дополнительно с "принципом инверсии зависимостей"
Возможно я не понял изначально вопроса, прошу прощения.
Мы убирали связь реализации инфраструктуры от бизнес логики, но не связь как таковую вообще.
"Пропихнуть" экземпляр вместо интерфейса нам ничего не мешает. Так же как нам ничего не мешает вообще не делать интерфейсов, слоев и абстракций. Это все условные правила которых мы придерживаемся что бы добиться "простоты" использования, расширяемости, сопровождения и тд.
Тут уже фундаментальное поле для дискуссий — оправдано ли существование вообще хоть какой либо архитектуры.
Спасибо за интерес к статье!
Ознакомьтесь пожалуйста с "принципом инверсии зависимостей". Он даст вам понять почему зависимость от интерфейса != зависимости от реализации интерфейса и поможет разобраться в примерах.
Большое спасибо за такой развернутый комментарий!
Постараемся учесть все замечания в будущем
В DDD для этого рекомендуют делать и выносить отдельные мапперы, как мы и сделали в итоге: маппер из схемы БД в домен и обратно, и маппер с dto в доменный объект и обратно
Почти весь nest на декораторах держится) https://docs.nestjs.com/techniques/validation
Если для сборки доменной сущности нужно много join'ов — это нормально, и это допустимо внутри именно репозитория, потому что задача репозитория — восстановить агрегат из хранилища.
Главное правило: обо всей этой сложности домен знать не должен, а сложность инфраструктуры не должна лезть в домен.
Как база архитектуры вообще — Роберт Мартин "Чистая архитектура"
А если по DDD то, наверное, Вон Вернон "Domain-Driven Design Distilled" или Влад Хононов "Learning Domain-Driven Design: Aligning Software Architecture and Business Strategy"
Если честно, не искал.
Но гугл не дал никаких положительных результатов
Насчет Interface, интересное замечание. В нашем случае у нас там сложены все dto для валидации входных данных с внешнего мира, т.е. с rest хендлеров (в nest.js это классы с стандартными декораторами полей из class-validator). Т.к их достаточно много, выглядело удобно выделить в отдельный слой.
А что касается NestJS, да он действительно вдохновлён Spring и использует похожие подходы: DI-контейнер, декораторы, модули. Но это не обязательно минус - для некоторых проектов это даёт хорошую масштабируемость и структурированность. "ад зависимостей" — хаос можно устроить в любом проекте и на любой платформе. В этом мире все стремится к энтропии))
Именно. Это и послужило одной из мотиваций
Если взять легаси в 1000 строк, созданный за 7 человеко-дней, то переход на архитектуру с слоями и DDD-Lite мог занять порядка 10-12 человеко-дней. При этом мы смогли сохранить примерно 50–60% существующего кода, который уже отвечал базовым требованиям качества. Скорость внедрения новых фич увеличилась примерно на 20–25%.
Считали как
), а после изменений — 4 дня (
). Тогда получается что: 
Если раньше на новую функциональность уходило бы примерно 5 дней (
Учитывая вариации в оценках и специфику задач, мы наблюдали увеличение скорости внедрения новых фич в диапазоне 25–30%. Эти цифры подтверждаются сокращением времени от идеи до выпуска, ну и самое главное, снижением количества ошибок и скорости погружения в проблематику задачи.