<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 18:52:14 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="ToR_tsir3K_sylzmKSHgREKbkqi5imzsGL-cEALwvYihVbSBOtVxz1qJeH4lLhAzgpK_ArG9kk6lXwZEUPda5g";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Предметная область | JS: Предметно-ориентированное проектирование</title>
<meta name="description" content="Предметная область / JS: Предметно-ориентированное проектирование: Разбираемся, что такое Domain-Driven Design (предметно-ориентированное проектирование) и ограниченный контекст">
<link rel="canonical" href="https://ru.hexlet.io/courses/js-ddd/lessons/domain/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Предметная область">
<meta property="og:title" content="JS: Предметно-ориентированное проектирование">
<meta property="og:description" content="Предметная область / JS: Предметно-ориентированное проектирование: Разбираемся, что такое Domain-Driven Design (предметно-ориентированное проектирование) и ограниченный контекст">
<meta property="og:url" content="https://ru.hexlet.io/courses/js-ddd/lessons/domain/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="22UqL0krL2gcNengJq3ZJAebKJ2px323jeEXHLKPK_80tOEYu1WCCKp2zXgqoilTx5IFN6HwgxUwAY1I4IjMkQ" />
<script src="/vite/assets/inertia-DfXos102.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-cb8xch9l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDAwNywicHVyIjoiYmxvYl9pZCJ9fQ==--f0b38f0e25ed59255acec6eaeaeec0a99aec453f/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Binary%20code-rafiki.png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T18:52:14.161Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"fFaS7l99xWYlJRz4X0PzQ7M8dN5hT5lWatxNrl6dDYWTh1nZrQNoBpNmOGBTTAM0czVZdGl4Z_TXPNf6DJrq6w","topics":[{"id":49523,"title":"Что нам предоставляет `{ exception: true }` в валидации транзакции? В текущей реализации валидатора ведь нет опций.","plain_title":"Что нам предоставляет { exception: true } в валидации транзакции? В текущей реализации валидатора ведь нет опций. ","creator":{"public_name":"Алексей Черняев","id":238366,"is_tutor":false},"comments":[{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":106226,"body":"Приветствую, Алексей!\n\nАга, этого не должно быть тут, похоже остался артефакт от предыдущей реализации. Поправил. Спасибо, что обратили внимание.","topic_id":49523}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Предметная область","entity_url":null,"active":true}},{"id":51605,"title":"Вопрос по решению учителя.\n\nЕсли я правильно понимаю, то валидация capitalTransaction не повлияет на сохранение ticket и capitalTransaction в репозитории, т.к. ошибка не обрабатывается, однако сохранение не должно происходить. Верно?\n\n_src/services/MoneyService.js_\n\n_Метод buyTicket_\n\n\n```\nconst capitalTransaction = new this.entities.CapitalTransaction(ticket);\nthis.validate(capitalTransaction);\n```","plain_title":"Вопрос по решению учителя. Если я правильно понимаю, то валидация capitalTransaction не повлияет на сохранение ticket и capitalTransaction в репозитории, т.к. ошибка не обрабатывается, однако сохранения не должно происходить. Верно? src/services/MoneyService.js Метод buyTicket const capitalTransaction = new this.entities.CapitalTransaction(ticket); this.validate(capitalTransaction); ","creator":{"public_name":"Адель","id":63047,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":110442,"body":"Ага спасибо, тут поправить нужно.","topic_id":51605}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Предметная область","entity_url":null,"active":true}},{"id":27446,"title":" const satuday = new Date('1.04.2017');\n price.calculateFor(satuday); // 650\n\nв целом ерунда, но saturday написано с ошибкой)","plain_title":" const satuday = new Date('1.04.2017'); price.calculateFor(satuday); // 650 в целом ерунда, но saturday написано с ошибкой) ","creator":{"public_name":"Александр Кириллов","id":190404,"is_tutor":false},"comments":[{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":58944,"body":"Большое дело состоит из мелочей :) Поправил. Спасибо!","topic_id":27446}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Предметная область","entity_url":null,"active":true}},{"id":23416,"title":"Есть несколько вопросов:\n\n1. Зачем ```Price``` создавать в виде отдельной сущности, сохранять в отдельном репозитории ```PriceRepository```, потом искать там по ```CinemaHall```,\nесли '_Price связан с CinemaHall как o2o_' и в такой постановке ```Price``` может быть просто атрибутом ```CinemaHall```\n2. Зачем в ```CapitalTransaction``` нужна валидация '_Свойство cost должно существовать и быть числом_' - это уже обеспечивается валидацией в ```Ticket```?\n3. Зачем классы засовывать в ```bottle.entities``` и потом использовать конструкцию ```new this.entities.Class``` вместо ```new Class```? что это дает?\n4. Почему файлы BaseEntity.js, BaseRepository.js и BaseService.js лежат в директории ```lib```, \nа не в директориях ```entities```, ```repositories```, ```services``` соответственно.\nПри этом пустые (так и хочется спросить зачем они нужны) ApplicationEntity.js, ApplicationRepository.js и ApplicationService.js спокойно там лежат.\n\n","plain_title":"Есть несколько вопросов: Зачем Price создавать в виде отдельной сущности, сохранять в отдельном репозитории PriceRepository, потом искать там по CinemaHall, если 'Price связан с CinemaHall как o2o' и в такой постановке Price может быть просто атрибутом CinemaHall Зачем в CapitalTransaction нужна валидация 'Свойство cost должно существовать и быть числом' - это уже обеспечивается валидацией в Ticket? Зачем классы засовывать в bottle.entities и потом использовать конструкцию new this.entities.Class вместо new Class? что это дает? Почему файлы BaseEntity.js, BaseRepository.js и BaseService.js лежат в директории lib, а не в директориях entities, repositories, services соответственно. При этом пустые (так и хочется спросить зачем они нужны) ApplicationEntity.js, ApplicationRepository.js и ApplicationService.js спокойно там лежат. ","creator":{"public_name":"Эдуард Кирютин","id":193581,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":50029,"body":"1. Если сделать его атрибутом и затем, когда-нибудь поменять, то мы нарушим согласованность старых данных. Цены нельзя просто так менять, их надо либо делать чере з связи, либо копировать в _Order_.\n2. Это чисто технический момент. Каждое поле обязано содержать свою валидацию.\n3. Дает главную фишку ооп - полиморфизм. Тут стоит внимательнее прочитать про IOC.\n4. Это прием использующийся рядом фреймворков. В нашем приложении кода мало, поэтому кажется что вариант избыточный, но в реальных приложениях это не так. Base содержит базовую логику, предоставляемую фреймворком/библиотекой, Application, базовый класс уже на уровне приложения, он позволяет легко расширять возможности при условии что все наследуются от него. По дефолту этот класс пустой, дальше по ситуации.\n","topic_id":23416}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Предметная область","entity_url":null,"active":true}},{"id":21261,"title":"хотел спросить по `DDD`, в частности по ограниченным контекстам.. в общем-то понятно, что `DDD` это больше про то как правильно моделировать предметную область, как взаимодействовать команде разработчиков и экспертам из предметной области и т.д., и отсюда вполне ясно для чего нам ограниченные контексты.. но!\n\nвот смоделировали мы значит контексты, и как потом этим пользоваться в коде, у нас получится несколько доменов, перенесенных в код? на каждый контекст свой домен, а общение между доменами производить через события допустим.. и уже эту совокупность доменов у нас будет оборачивать общий слой сервисов и инфраструктуры?\n\nили вообще логичнее, чтобы каждый домен оборачивался своим слоем сервисов и инфраструктуры, через которую организовывается взаимодействие?\n\nили у нас получится один домен куда сольются все контексты, и допустим если в каких-то контекстах были сущности с одинаковыми названиями, то в домене итоговом это будут ужасные мутантоподобные сущности со сборной солянкой свойств и методов, полностью нарушающие SRP (ну это я думаю самый бредовый вариант, но его стоило упомянуть) (:\n\nкорни то вопроса в общем понятны, ведь это частый случай, когда у нас в системе есть довольно неоднозначные сущности, ну типа `Менеджер`, в подсистеме управления продажами он рассматривается по своему, в подсистеме начисления зарплаты по своему, в подсистеме безопасности это вообще обычный юзер с набором прав к примеру... а по факту в сущность пихают все что есть, и там получается такая каша методов и данных.. (: хоть начинать держать данные отдельно от операций над ними (:","plain_title":"хотел спросить по DDD, в частности по ограниченным контекстам.. в общем-то понятно, что DDD это больше про то как правильно моделировать предметную область, как взаимодействовать команде разработчиков и экспертам из предметной области и т.д., и отсюда вполне ясно для чего нам ограниченные контексты.. но! вот смоделировали мы значит контексты, и как потом этим пользоваться в коде, у нас получится несколько доменов, перенесенных в код? на каждый контекст свой домен, а общение между доменами производить через события допустим.. и уже эту совокупность доменов у нас будет оборачивать общий слой сервисов и инфраструктуры? или вообще логичнее, чтобы каждый домен оборачивался своим слоем сервисов и инфраструктуры, через которую организовывается взаимодействие? или у нас получится один домен куда сольются все контексты, и допустим если в каких-то контекстах были сущности с одинаковыми названиями, то в домене итоговом это будут ужасные мутантоподобные сущности со сборной солянкой свойств и методов, полностью нарушающие SRP (ну это я думаю самый бредовый вариант, но его стоило упомянуть) (: корни то вопроса в общем понятны, ведь это частый случай, когда у нас в системе есть довольно неоднозначные сущности, ну типа Менеджер, в подсистеме управления продажами он рассматривается по своему, в подсистеме начисления зарплаты по своему, в подсистеме безопасности это вообще обычный юзер с набором прав к примеру... а по факту в сущность пихают все что есть, и там получается такая каша методов и данных.. (: хоть начинать держать данные отдельно от операций над ними (: ","creator":{"public_name":"Nikita","id":184506,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":45303,"body":"Это всегда идет из задачи. Если говорить про те же деньги то смысла в таблице нет никакого (для большинства ситуаций). Если кто-то покупает товар, то происходит копирование этой суммы в заказ, потому что она может поменяться, а заказ должен знать о том какая сумма была на момент покупки.","topic_id":21261},{"creator":{"public_name":"Nikita","id":184506,"is_tutor":false},"id":45266,"body":"спасибо за ответ, так сказать с высоты огромного опыта за плечами (:\n\nа можно еще задать вопрос, теперь уже более с более узким смыслом.. касаемо объектов-значений, и их хранения в базе, как лучше? выделять их в отдельную таблицу, и допустим для идентификации использовать все поля как составной первичный ключ? или лучше в таблице сущности, к которой относится данный объект-значение добавлять колонки?\n\nну вот допустим пример из статью про `DDD`, объект сущность сумма денег:\n```\n{\n amount: 5000,\n currency: 'us dollars',\n}\n```\nденежная сумма 5000 долларов, состоит из непосредственно количества и валюты, есть смысл создавать таблицу в базе допустим \"Money\" для хранения уникальных сочетаний сумм и валют, с полями:\n1) amount numeric DEFAULT 0,\n2) currency character varying DEFAULT 'us dollars',\n3) PRIMARY KEY (amount, currency);\n\nили логичнее просто в таблице сущности, например, \"banking_accounts\" сделать соответствующие поля...\n\nв общем пока писал вопрос, сам решил, что логичнее делать специальную таблицу, потому что денежные суммы могут начать использоваться в разных сущностях и операциях (:","topic_id":21261},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":45125,"body":"В повседневном программировании все прозаичнее. Есть те кто упарываются по чистоте, и сами фигачат все абстракции, есть другие, типа нас, которые балансируют между \"сделать по красоте\" и \"фреймворк предлагает вот такие варианты (orm в первую очередь)\". Я не верю в то что можно построить стройную идеальную систему с четкими разделениями контекстов, нафигачить сервисов и жить не тужить. Чем сложнее система, тем меньше шансов что вы поймете как правильно ей крутить, так как она не умещается в голове. ","topic_id":21261}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Предметная область","entity_url":null,"active":true}},{"id":9455,"title":"Показывает решение учителя для следующего упражнения (урок `Мутация`), а в `Мутациях` решение учителя пустое","plain_title":"Показывает решение учителя для следующего упражнения (урок Мутация), а в Мутациях решение учителя пустое ","creator":{"public_name":"Кирилл Вишенков","id":130072,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":19391,"body":"сделал!","topic_id":9455}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Предметная область","entity_url":null,"active":true}},{"id":30565,"title":"по сути все сделал похоже. [ревью](https://ru.hexlet.io/code_reviews/135220). \n\nправда совершенно упустил валидацию `capitalTransaction`:\n```\nthis.validate(capitalTransaction, { exception: true });\n```\nну и без `date-fns` все вполне просто решается, у `Date` уже достаточно методов для решения. Но это мелочи.\n\nГлавное, что меня беспокоит, что с каждым следующим заданием я уже не могу держать в голове все это вместе, и где что лежит и как с чем связано, я уже порой не понимаю. И сижу в этом ковыряюсь как заново. В реальной жизни видимо такие штуки вообще невозможно контролировать полностью и это как-то напрягает. \n","plain_title":"по сути все сделал похоже. ревью (https://ru.hexlet.io/code_reviews/135220). правда совершенно упустил валидацию capitalTransaction: this.validate(capitalTransaction, { exception: true }); ну и без date-fns все вполне просто решается, у Date уже достаточно методов для решения. Но это мелочи. Главное, что меня беспокоит, что с каждым следующим заданием я уже не могу держать в голове все это вместе, и где что лежит и как с чем связано, я уже порой не понимаю. И сижу в этом ковыряюсь как заново. В реальной жизни видимо такие штуки вообще невозможно контролировать полностью и это как-то напрягает. ","creator":{"public_name":"Сергей Егупов","id":184023,"is_tutor":false},"comments":[{"creator":{"public_name":"Dmitry Smirnoff","id":228665,"is_tutor":false},"id":75078,"body":"**Kirill Mokevnin**, а почему мы все проверки вызовов валидаторов делаем через if, а не через выбрасываение exception'a, как с CapitalTransaction?\n\n","topic_id":30565},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":66365,"body":"> . В реальной жизни видимо такие штуки вообще невозможно контролировать полностью и это как-то напрягает.\n\nИменно так, в реальной жизни файлов не десять, а сотни и тысячи, а то и десятки тысяч. Кода сотни тысяч и миллионы строк кода. А еще десятки и сотни (иногда тысячи разных сервисов). Знать это все невозможно в принципе.","topic_id":30565}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Предметная область","entity_url":null,"active":true}},{"id":34454,"title":"Возможно, я пропустил в уроках ранее (есть такое ощущение) но не могу найти - просмотрел предыдущие уроки.\nЧто за валидатор association? Он у учителя в constraints для CapitalTransaction. Не удается понять, для чего он нужен. Вижу его описание в модуле валидатор, но при значении параметра true он возвращает undefined? \n\nПомогите, заблудился совсем :(\n","plain_title":"Возможно, я пропустил в уроках ранее (есть такое ощущение) но не могу найти - просмотрел предыдущие уроки. Что за валидатор association? Он у учителя в constraints для CapitalTransaction. Не удается понять, для чего он нужен. Вижу его описание в модуле валидатор, но при значении параметра true он возвращает undefined? Помогите, заблудился совсем :( ","creator":{"public_name":"Dmitry Smirnoff","id":228665,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":75209,"body":"> entityValidator(value);\n\nЭто валидация value, которое в данном случае зависимая сущность.","topic_id":34454},{"creator":{"public_name":"Dmitry Smirnoff","id":228665,"is_tutor":false},"id":75211,"body":"**Kirill Mokevnin**, вот это я и не могу понять - почему это именно зависимая сущность? где этот увидеть, откуда понять?","topic_id":34454},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":75212,"body":"> почему это именно зависимая сущность? где этот увидеть, откуда понять?\n\nУвидеть в моем ответе)\n\nВот пример из реальной жизни https://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_associated","topic_id":34454},{"creator":{"public_name":"Dmitry Smirnoff","id":228665,"is_tutor":false},"id":75208,"body":"**Kirill Mokevnin**,да вот в том-то и дело, что не понимаю как работает :(\n```\nvalidate.validators.association = (value) => {\n if (!value) {\n return null;\n }\n return entityValidator(value);\n };\n```\nкаким образом ему на вход именно зависимая сущность попадает? ","topic_id":34454},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":75199,"body":"Этот валидатор позволяет добавить в валидацию проверку валидности зависимой сущности (по умолчанию проверяется только сама сущность, а не ее зависимости). Посмотрите исходник _lib/validator.js_. Там видно что он берет нужную сущность и начинает ее валидировать по ее правилам.","topic_id":34454},{"creator":{"public_name":"Dmitry Smirnoff","id":228665,"is_tutor":false},"id":75228,"body":"**Kirill Mokevnin**, разобрался, спасибо. надо было плотнее поковыряться как собственно происходит отработка constraints, откуда это value на вход метода берется.\n","topic_id":34454}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Предметная область","entity_url":null,"active":true}},{"id":34452,"title":"Линтер ругается на учительский код:\n\n```\nusr/src/app/src/index.js\n11:7 error Expected no linebreak before this expression implicit-arrow-linebreak\n16:30 error Expected parentheses around arrow function argument arrow-parens\n\n/usr/src/app/src/lib/BaseRepository.js\n11:35 error Expected parentheses around arrow function argument arrow-parens\nusr/src/app/src/index.js\n11:7 error Expected no linebreak before this expression implicit-arrow-linebreak\n16:30 error Expected parentheses around arrow function argument arrow-parens\n\n/usr/src/app/src/lib/BaseRepository.js\n11:35 error Expected parentheses around arrow function argument arrow-parens\n```\n","plain_title":"Линтер ругается на учительский код: usr/src/app/src/index.js 11:7 error Expected no linebreak before this expression implicit-arrow-linebreak 16:30 error Expected parentheses around arrow function argument arrow-parens /usr/src/app/src/lib/BaseRepository.js 11:35 error Expected parentheses around arrow function argument arrow-parens usr/src/app/src/index.js 11:7 error Expected no linebreak before this expression implicit-arrow-linebreak 16:30 error Expected parentheses around arrow function argument arrow-parens /usr/src/app/src/lib/BaseRepository.js 11:35 error Expected parentheses around arrow function argument arrow-parens ","creator":{"public_name":"Dmitry Smirnoff","id":228665,"is_tutor":false},"comments":[{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":75112,"body":"**Dmitry Smirnoff**, приветствую!\n\nВозможно у вас загружена старая версия упражнения. Попробуйте выполнить сброс, только вначале сохраните ваш код (так как при сбросе он будет удален). Так вы получите новую версию упражнения.","topic_id":34452}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Предметная область","entity_url":null,"active":true}},{"id":10549,"title":"Все-таки тест на создание сущности (в частности, createFilmScreening) лучше делать с проверкой всех параметров, а еще лучше - проверять его наличие в репозитории. А то случилось накосячить с cost, экземпляр создавался, и тест проходил (правда, валился другой), хотя в репозиторий не попадал не пройдя валидацию. Ведь понятие \"создание сущности\" в первую очередь ассоциируется с прохождением валидации и благополучным размещением в репозитории (по крайней мере, у меня). \nВ принципе, можно и забить, это я так, побрюзжать... :)","plain_title":"Все-таки тест на создание сущности (в частности, createFilmScreening) лучше делать с проверкой всех параметров, а еще лучше - проверять его наличие в репозитории. А то случилось накосячить с cost, экземпляр создавался, и тест проходил (правда, валился другой), хотя в репозиторий не попадал не пройдя валидацию. Ведь понятие \"создание сущности\" в первую очередь ассоциируется с прохождением валидации и благополучным размещением в репозитории (по крайней мере, у меня). В принципе, можно и забить, это я так, побрюзжать... :) ","creator":{"public_name":"Andy","id":117329,"is_tutor":false},"comments":[{"creator":{"public_name":"Andy","id":117329,"is_tutor":false},"id":21889,"body":"В конец теста добавить пару строчек:\n```\nconst filmScreening = repositories.FilmScreening.find(localFilmScreening.id);\nexpect(localFilmScreening).toMatchObject(filmScreening);\n``` \n \nОстальные сущности вроде можно оставить так, они перекрестным опылением покрываются (хотя, это не очень здорово, лучше бы не надеяться на зависимости). Вообще, конечно, и само офф. решение халявное. Тот же capitalTransaction полагается на зависимости и не проверяется на ошибки валидации. Его лучше бы вынести в отдельную функцию (полезно для следующего задания), как createFilmScreening или createPrice. Кстати, сейчас заметил, что createPrice отсутствует в решении. ","topic_id":10549},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":21950,"body":"Ага, добавил","topic_id":10549},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":21864,"body":"Напишешь тест? Я его добавлю.","topic_id":10549}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Предметная область","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":583,"slug":"js_ddd_domain_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"В этом упражнении мы добавим в домен две сущности `Price` и `CapitalTransaction`.\n\n`Price` - это цена за билет с привязкой к конкретному залу. Это не единственный возможный вариант формирования цены, но в нашем кинотеатре залы различаются по уровню комфорта. Кроме этого цена увеличивается в выходные. Увеличение происходит по формуле _price * weekendMultiplier_, где _weekendMultiplier_ это постоянный и единый для всех коэффициент,\nравный _1.3_.\n\n`Price` связан с `CinemaHall` как `o2o`.\n\n```javascript\nconst value = 500\nconst price = new Price(cinemaHall, value)\n\nconst friday = new Date('31.03.2017')\nprice.calculateFor(friday) // 500\nconst saturday = new Date('1.04.2017')\nprice.calculateFor(saturday) // 650\n```\n\nВторая сущность `CapitalTransaction` - представляет из себя движение денежных средств. Каждый раз когда продается билет, создается `CapitalTransaction` с привязкой к билету и его стоимости. В будущем эта же сущность будет использоваться и при возврате билета. Она отражает реальный приход и уход денег. Особенно это актуально с применением скидок или частичным возвратом стоимости.\n\nЭто разделение позволяет нам избавится от изменений сущности `ticket`. Любые манипуляции с билетом будут приводить к создании новой `CapitalTransaction`. Таким образом упрощается код и сохраняется история действий.\n\n```javascript\nconst capitalTransaction = new CapitalTransaction(ticket)\ncapitalTransaction.cost == ticket.cost // true\n```\n\n## src/entities/Price.js\n\nРеализуйте сущность `Price`. Она должна уметь рассчитывать цену билета на основе даты сеанса.\n\nВалидация:\n\n* Свойство `cinemaHall` должно существовать и быть уникальным\n* Свойство `value` должно существовать и быть числом\n\n## src/entities/CapitalTransaction.js\n\nРеализуйте сущность `CapitalTransaction`.\n\n* Свойство `createdAt` равное текущей дате на момент создания сущности\n\nВалидация:\n\n* Свойство `ticket` должно существовать\n* Свойство `cost` должно существовать и быть числом\n\n## src/services/MoneyService.js\n\nРеализуйте следующие бизнес-сценарии:\n\n### Создание сеанса фильма\n\nНа этом этапе сеанс фильма создается с привязкой к стоимости, рассчитанной на основе `Price` того зала, в котором проходит сеанс.\n\n```javascript\nconst [filmScreening] = services.MoneyService\n .createFilmScreening(film.id, cinemaHall.id, time)\n```\n\n### Покупка билета\n\nКроме самого билета должна создавать `CapitalTransaction`.\n\n```javascript\nconst [ticket] = moneyService.buyTicket(user.id, filmScreening.id, place)\nconst capital = repositories.capitalTransaction.findBy({ ticket })\ncapital.ticket === ticket // true\ncapital.cost === ticket.cost // true\n```\n","prepared_readme":"В этом упражнении мы добавим в домен две сущности `Price` и `CapitalTransaction`.\n\n`Price` - это цена за билет с привязкой к конкретному залу. Это не единственный возможный вариант формирования цены, но в нашем кинотеатре залы различаются по уровню комфорта. Кроме этого цена увеличивается в выходные. Увеличение происходит по формуле _price * weekendMultiplier_, где _weekendMultiplier_ это постоянный и единый для всех коэффициент,\nравный _1.3_.\n\n`Price` связан с `CinemaHall` как `o2o`.\n\n```javascript\nconst value = 500\nconst price = new Price(cinemaHall, value)\n\nconst friday = new Date('31.03.2017')\nprice.calculateFor(friday) // 500\nconst saturday = new Date('1.04.2017')\nprice.calculateFor(saturday) // 650\n```\n\nВторая сущность `CapitalTransaction` - представляет из себя движение денежных средств. Каждый раз когда продается билет, создается `CapitalTransaction` с привязкой к билету и его стоимости. В будущем эта же сущность будет использоваться и при возврате билета. Она отражает реальный приход и уход денег. Особенно это актуально с применением скидок или частичным возвратом стоимости.\n\nЭто разделение позволяет нам избавится от изменений сущности `ticket`. Любые манипуляции с билетом будут приводить к создании новой `CapitalTransaction`. Таким образом упрощается код и сохраняется история действий.\n\n```javascript\nconst capitalTransaction = new CapitalTransaction(ticket)\ncapitalTransaction.cost == ticket.cost // true\n```\n\n## src/entities/Price.js\n\nРеализуйте сущность `Price`. Она должна уметь рассчитывать цену билета на основе даты сеанса.\n\nВалидация:\n\n* Свойство `cinemaHall` должно существовать и быть уникальным\n* Свойство `value` должно существовать и быть числом\n\n## src/entities/CapitalTransaction.js\n\nРеализуйте сущность `CapitalTransaction`.\n\n* Свойство `createdAt` равное текущей дате на момент создания сущности\n\nВалидация:\n\n* Свойство `ticket` должно существовать\n* Свойство `cost` должно существовать и быть числом\n\n## src/services/MoneyService.js\n\nРеализуйте следующие бизнес-сценарии:\n\n### Создание сеанса фильма\n\nНа этом этапе сеанс фильма создается с привязкой к стоимости, рассчитанной на основе `Price` того зала, в котором проходит сеанс.\n\n```javascript\nconst [filmScreening] = services.MoneyService\n .createFilmScreening(film.id, cinemaHall.id, time)\n```\n\n### Покупка билета\n\nКроме самого билета должна создавать `CapitalTransaction`.\n\n```javascript\nconst [ticket] = moneyService.buyTicket(user.id, filmScreening.id, place)\nconst capital = repositories.capitalTransaction.findBy({ ticket })\ncapital.ticket === ticket // true\ncapital.cost === ticket.cost // true\n```\n","has_solution":true,"entity_name":"Предметная область"},"units":[{"id":1723,"name":"theory","url":"/courses/js-ddd/lessons/domain/theory_unit"},{"id":2488,"name":"quiz","url":"/courses/js-ddd/lessons/domain/quiz_unit"},{"id":1728,"name":"exercise","url":"/courses/js-ddd/lessons/domain/exercise_unit"}],"links":[{"id":425604,"name":"Доклад Кирилла “Ментальное программирование 2”\n","url":"https://www.youtube.com/watch?v=vkUTX1hruF8"},{"id":425605,"name":"Domain-Driven Design: стратегическое проектирование. Часть 1\n","url":"https://habrahabr.ru/post/316438/\n"}],"ordered_units":[{"id":1723,"name":"theory","url":"/courses/js-ddd/lessons/domain/theory_unit"},{"id":2488,"name":"quiz","url":"/courses/js-ddd/lessons/domain/quiz_unit"},{"id":1728,"name":"exercise","url":"/courses/js-ddd/lessons/domain/exercise_unit"}],"id":844,"slug":"domain","state":"approved","name":"Предметная область","course_order":40,"goal":"Разбираемся, что такое Domain-Driven Design (предметно-ориентированное проектирование) и ограниченный контекст","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"> Предметно-ориентированное проектирование (Domain-driven design) -\n> это набор принципов и схем, направленных на создание оптимальных систем объектов.\n> Сводится к созданию программных абстракций, которые называются моделями предметных областей.\n> В эти модели входит бизнес-логика, устанавливающая связь между реальными условиями области\n> применения продукта и кодом.\n\nДанный термин был впервые введён Э. Эвансом в его книге с таким же названием «Domain-Driven Design».\n\n\n\nПредставьте себе, что перед вами поставили задачу разработать биллинг (система тарификации,\nвыставление счетов, обработка платежей) для интернет-провайдера. С чего вы начнёте проектирование\nтакой системы? А начать надо с анализа предметной области.\nПознакомиться с основными сущностями системы и их взаимоотношениями, другими словами, вам будет\nнеобходимо разобраться в онтологии предметной области.\n\n\n\nИ вот тут на сцену выходит `DDD`. Центральная идея этого подхода заключается в том, что разработчики\nпостоянно активно сотрудничают с экспертами предметной области (со стороны заказчика) и вместе\nс ними формируют так называемый единый язык. Этот язык будет использоваться для общения\nмежду всеми членами команды, а позже отразится в исходном коде разрабатываемой программы.\n\n`Ubiquitous Language` — это не бизнес-жаргон, навязанный разработчикам, а настоящий язык,\nсозданный целостной командой – экспертами в предметной области, разработчиками, бизнес-аналитиками\nи всеми, кто вовлечён в создание системы. Роль в команде не столь существенна, поскольку каждый\nчлен команды использует для описания проекта единый язык. Процесс создания единого языка более\nтворческий, чем формальный, так как он, как и любой другой естественный язык, постоянно развивается,\nа те артефакты, которые вначале способствовали разработке полезного единого языка, со временем устаревают.\nВ итоге остаются только самые устойчивые и проверенные элементы.\n\nНаиболее важное для разработчика – это умение слушать экспертов, получать максимальное количество полезных\nзнаний о предметной области. В то же время эксперты также должны прислушиваться к разработчикам и\nих пожеланиям. Команда учится и растёт вместе, если она действует сплочённо, получая более\nглубокое понимание бизнеса.\n\n## Bounded context\n\nЭто второе по значимости свойство DDD после единого языка. Оба эти понятия взаимосвязаны и\nне могут существовать друг без друга.\n\nИтак, ограниченный контекст – это явная граница, внутри которой существует модель предметной области,\nкоторая отображает единый язык в модель программного обеспечения.\n\nВ каждом ограниченном контексте существует только один единый язык.\n\n* Ограниченные контексты являются относительно небольшими.\n* Ограниченный контекст достаточно велик только для единого языка изолированной предметной области, но не больше.\n* Единый значит «вездесущий» или «повсеместный», т. е. язык, на котором говорят члены команды\n и на котором выражается отдельная модель предметной области, которую разрабатывает команда.\n* Язык является единым только в рамках команды, работающей над проектом в едином ограниченном контексте.\n* Попытка применить единый язык в рамках всего предприятия или что хуже, среди нескольких\n предприятий, закончится провалом.\n\n\n\nНапример, система биллинга крупной телекоммуникационной компании может иметь следующие\nключевые элементы (контексты):\n\n* Клиентское обслуживание\n* Система безопасности и защиты\n* Резервное копирование\n* Взаимодействие с платёжными системами\n* Ведение отчётности\n* Система уведомлений\n\nВ этом уроке были представлены самые базовые понятия и идеи предметно-ориентированного проектирования.\nЗа более подробным описанием можно обратиться к [отличной статье](https://habrahabr.ru/post/316438/)\nна Хабре, либо к книге Эрика Эванса. Также не могу не порекомендовать\n[моё выступление](https://www.youtube.com/watch?v=vkUTX1hruF8) на одной из региональных конференций,\nв котором я рассказываю о похожих идеях, помогающих улучшить качество кода, который мы пишем.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":1727,"name":"theory","url":"/courses/js-ddd/lessons/intro/theory_unit"}],"links":[],"ordered_units":[{"id":1727,"name":"theory","url":"/courses/js-ddd/lessons/intro/theory_unit"}],"id":848,"slug":"intro","state":"approved","name":"Введение","course_order":10,"goal":"Знакомимся с целями и задачами курса","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Обычно во вступлении мы рассказываем то, что ожидает вас внутри курса, но здесь я решил\nрассказать кое-что важное. Попробуйте самостоятельно ответить на вопрос. Какая основная\nзадача программиста?\n\nВероятно, вы ответите \"писать код\" и будете не правы. Писать код это всего лишь средство,\nпричём не единственное. Также часто решением задачи является удаление кода или, вообще,\nотсутствие кода, и всё это тоже область компетенции программиста.\n\nНачать нужно с того, что программирование, как таковое, это не цель, это всего лишь\nсредство достижения бизнес-целей той компании, которая вас нанимает. В конечном итоге\nвсё программное обеспечение так или иначе служит удовлетворению потребностей бизнеса:\nувеличению прибыли, снижению издержек. [Хорошая статья об этом](https://ru.hexlet.io/blog/posts/developers-business-value) есть в нашем блоге.\n\nНа практике это означает очень простую вещь, перед тем как бросаться писать код,\nнужно понять цель того, что вам нужно сделать. Хочу ещё раз акцентировать ваше внимание, на том,\nчто цель это **зачем** мы это делаем, а не **что** нужно сделать. У меня\nесть хорошая аналогия, которую мы постоянно наблюдаем в своей жизни. Вспомните приходы\nк доктору. Многие люди пытаются рассказывать доктору не только симптомы, но и\nвыдвигают гипотезы, а некоторые прямо утверждают, что у них конкретная болезнь и, более того,\nони знают, как лечиться. Доктора обычно пропускают это мимо ушей, потому что его\nзадача понять истинную причину. То же самое часто происходит в разработке. К вам приходит\nзаказчик и говорит, **что** нужно сделать. Например: \"Вася, добавь две колонки в базу\".\nВозникает парадоксальная ситуация, чем более технически подкован заказчик тем, как правило,\nон больше пытается продавливать конкретные решения, вместо того, чтобы описывать свою\nбизнес-задачу (цель), оставляя вам манёвр для решения.\n\nИзбежать этого невозможно, никто и никогда не будет давать идеальных задач, которые\nсозданы исходя из бизнес-целей. Такое, конечно же, бывает, но гораздо реже, чем вам\nможет показаться. В итоге бизнес-аналитикой занимается разработчик (кроме сложных случаев),\nи это нормально. Докопавшись до сути, может оказаться так, что кода писать не надо вообще\nи достаточно поменять правила игры.\n\nДальше по курсу мы будем исходить из того, что все цели уже определены и нужно именно\nписать код, но перед тем, как мы двинемся дальше, я расскажу о том, как смотреть\nна мир глазами бизнеса и почему это полезно.\n\nПодумайте вот о чём. Откуда бизнес узнает, что нужно делать? Работая на дядю может сложиться\nвпечатление, что там наверху умные люди, которые знают, что делают. На самом деле они не знают.\nПредставьте, что вы начинаете с нуля свой стартап. После непродолжительного анализа станет\nпонятно, что основная сложность не в том, чтобы понять \"что делать\", а в том, чтобы понять\n\"что не делать\". На эту тему есть обязательная книга к прочтению, которая поменяет\nваше мировоззрение: \"Бизнес с нуля. Метод Lean Startup.\"\n\n## Lean Startup\n\n\n\nНе обращайте внимание на слово \"стартап\" в заголовке, эта методология одинаково хорошо работает\nи для больших бизнесов и для молодых проектов. Удивительно, но основная идея этого подхода\nпришла из научного мира и называется \"научный метод\":\n\n```\nНау́чный ме́тод — совокупность основных способов получения новых знаний и методов решения\nзадач в рамках любой науки.\n\nМетод включает в себя способы исследования феноменов, систематизацию, корректировку новых\nи полученных ранее знаний. Умозаключения и выводы делаются с помощью правил и принципов\nрассуждения на основе эмпирических (наблюдаемых и измеряемых) данных об объекте. Базой\nполучения данных являются наблюдения и эксперименты. Для объяснения наблюдаемых фактов\nвыдвигаются гипотезы и строятся теории, на основании которых в свою очередь строится\nматематическое описание — модель изучаемого объекта.\n```\n\nПервое. Логика контринтуитивна. Понять, что нужно вашим пользователям заранее и без общения\nс ними, практически невозможно. Используя lean startup мы выдвигаем гипотезы, а не\nпродумываем конкретные решения. Пример гипотез:\n\n```\nПользователи хотят заказывать такси без необходимости звонить оператору и диктовать адрес.\n\nПользователю удобнее оплачивать такси с карты, чем наличными\n```\n\nПроницательный читатель увидит, что при таком подходе, нет цели реализовать сразу всё, от и\nдо продумав все части программы. Задачей станет реализовать только то, что может помочь\nподтвердить или опровергнуть гипотезу. Ведь если гипотеза не верна, это автоматически означает,\nчто нужно корректировать все дальнейшие планы. В противном случае будут большие потери.\n\nПосле того, как гипотеза готова, делается всё необходимое для её проверки. Многие гипотезы,\nпо факту, не требуют написания кода вообще. Например, гипотеза про удобство оплаты такси\nкартой проверяется звонками друзьям/постами в соцсети. Согласитесь, что это сильно дешевле,\nпроще и быстрее, чем месяцами писать приложение, а потом увидеть, что это никому не нужно.\n\nНа выходе получается цепочка: Гипотеза -> Реализация (если нужно) -> Анализ данных. Повторяя\nэту цепочку снова и снова, мы получаем продукт, который действительно работает и отвечает\nбизнес-целям.\n\nКлючевые слова для самообразования:\n\n* Customer development\n* Business Model Canvas\n* Minimum Viable Product\n* Pivot\n\n## SMART\n\nКогда речь идёт про уже существующий бизнес или, даже, личные цели, то подойдёт такой\nподход как [SMART](https://ru.wikipedia.org/wiki/SMART):\n\n```\nЭто мнемоническая аббревиатура, используемая в менеджменте и проектном управлении для\nопределения целей и постановки задач:\n\n* конкретный (specific);\n* измеримый (measurable);\n* достижимый (attainable);\n* значимый (relevant);\n* соотносимый с конкретным сроком (time-bounded)\n```\n\nЭтот подход хорошо расписан в вики, поэтому не буду заниматься копипастой.\n\n## Impact mapping\n\n\n\nImpact Mapping простая и эффективная техника для определения целей заказчика\nи передача этих целей разработчикам.\n\nImpact Mapping — это диаграмма связей (mind map) по целям проекта с картой влияний, которые должны\nподтолкнуть бизнес заказчика к достижению целей.\n\n### Why?\n\nЦентральный элемент нашей карты, который отвечает на ключевой вопрос:\nЗачем мы это делаем? Это цель, которую бизнес пытается достичь.\n\n### Who?\n\nНа первом уровне мы отвечаем на вопросы: Кто поможет достичь желаемого результата?\nКто может помешать? Кто пользователи нашего продукта? Сюда войдут все заинтересованные стороны, которые могут повлиять на цели бизнеса.\n\n### How?\n\nНа втором уровне мы должны описать воздействия, которые должны оказать заинтересованные\nстороны, чтобы бизнес достиг целей. Мы ищем ответ на вопросы: Как они помогут бизнесу\nдостичь целей? Как они могут помешать успеху проекта?\n\n### What?\n\nПосле ответа на основные вопросы можно обсудить конкретные задачи. Третий уровень\nотвечает на вопросы: Что мы можем сделать как организация или команда разработки,\nчтобы создать необходимые воздействия? Здесь будет описан конечный результат нашей работы.\n\nПодробнее об этом подходе можно прочитать в\n[замечательной статье](https://habrahabr.ru/post/246401/) Александра Бындю на Хабре.\n\n## User Story Mapping\n\n\n\nПосле определения карты влияний на цели можно определить роли пользователей,\nкак они будут взаимодействовать с системой, важность задач, план релизов и т.д.\n\nЦель `user story mapping` в том, чтобы приоритезировать пользовательские истории\nпо важности.\n\nПример пользовательской истории:\n\n```\nЯ, как менеджер по продажам, хочу видеть отчёт по интересам клиентов в курсах, для того,\nчтобы принять решение о создании нового курса и приглашения этих клиентов принять в нём участие.\n```\n\nОб этой полезной технике можно найти много статей на просторах сети. Подробнее\nна ней останавливаться не будем, пора переходить к самому курсу).\n\n\n## Проект: Электронная продажа билетов\n\n\n\nНа протяжении курса мы будем создавать систему для продажи билетов в кинотеатре\nчерез интернет. Бизнес-анализ тоже будет присутствовать, но в очень ограниченном\nварианте. Основной упор на то, как писать код.\n\n## Основные темы\n\nПо пути разберём много страшных слов, и я понимаю, что многие вещи, о которых будет говориться,\nвызовут ещё больше вопросов, чем ответов. Цель этого курса показать новые горизонты,\nа не дать всеобъемлющее руководство к действию. Этим курсом ваш путь ��олько начинается.\n\n* Domain-Driven Design\n* Entity, Value-Object\n* Repository\n* Service Layer\n* Inversion Of Control\n* Dependency Inversion Principle\n* Dependency Injection Container\n* FSM\n\n## Дополнительные темы\n\nВ процессе используем множество разных библиотек, таких как:\n\n* bottlejs\n* uuid/validate.js\n* lodash/date-fns\n"},"id":139,"slug":"js-ddd","challenges_count":3,"name":"JS: Предметно-ориентированное проектирование","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы изучите предметно-ориентированное программирование. Вы узнаете больше об инверсии зависимостей и репозиториях. В итоге научитесь использовать шаблон Service Layer для разделения кода на слои. Знания из этого курса помогают программистам выделять правильные сущности и находить связи между ними.","kind":"advanced","updated_at":"2026-01-20T11:54:39.971Z","language":"javascript","duration_cache":30060,"skills":["Использовать предметно-ориентированный дизайн в своей повседневной практике","Грамотно переносить логику предметной области на код (сущности, сервисы)","Правильно строить архитектуру сложных бизнес-приложений, разделять код на слои в соответствии с шаблоном Service Layer","По максимуму использовать возможности ООП для организации легко расширяемого и тестируемого кода"],"keywords":["сущности","сервисы","репозитории","валидация","инверсия зависимостей"],"lessons_count":8,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTEzMiwicHVyIjoiYmxvYl9pZCJ9fQ==--1b8ef92e1ca01464452e31b4419760772ddac0c9/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJqcGciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--39ba06fa99226096df9fc6bb31f84e1d29ea98e9/image.png"},"recommendedLandings":[{"stack":{"id":71,"slug":"js-domain-driven-design","title":"DDD на Javascript","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"finished","order":4700,"duration_in_months":2},"id":127,"slug":"js-domain-driven-design","title":"DDD на Javascript","subtitle":"Навык ООП и предметно-ориентированного программирования для создания масштабируемого кода и карьерного роста","subtitle_for_lists":"Изучите ООП и DDD для создания масштабируемого кода","locale":"ru","current":true,"duration_in_months_text":"2 месяца","stack_slug":"js-domain-driven-design","price_text":"от 3 900 ₽","duration_text":"2 месяца","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDAwNywicHVyIjoiYmxvYl9pZCJ9fQ==--f0b38f0e25ed59255acec6eaeaeec0a99aec453f/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Binary%20code-rafiki.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/js-ddd/lessons/domain/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">JS: Предметно-ориентированное проектирование</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Предметная область</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Предметная область","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"JS: Предметно-ориентированное проектирование"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><blockquote>
<p>Предметно-ориентированное проектирование (Domain-driven design) -
это набор принципов и схем, направленных на создание оптимальных систем объектов.
Сводится к созданию программных абстракций, которые называются моделями предметных областей.
В эти модели входит бизнес-логика, устанавливающая связь между реальными условиями области
применения продукта и кодом.</p>
</blockquote>
<p>Данный термин был впервые введён Э. Эвансом в его книге с таким же названием «Domain-Driven Design».</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTE0MywicHVyIjoiYmxvYl9pZCJ9fQ==--abd90b96becb696699e4a591d7e330c717b15096/ddd.jpg" alt="ddd" loading="lazy"/></p>
<p>Представьте себе, что перед вами поставили задачу разработать биллинг (система тарификации,
выставление счетов, обработка платежей) для интернет-провайдера. С чего вы начнёте проектирование
такой системы? А начать надо с анализа предметной области.
Познакомиться с основными сущностями системы и их взаимоотношениями, другими словами, вам будет
необходимо разобраться в онтологии предметной области.</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTE0NCwicHVyIjoiYmxvYl9pZCJ9fQ==--b9fbde0f61d4ba5d1c8ab5799067127d86b42146/ubiquitous-lang.jpg" alt="ddd" loading="lazy"/></p>
<p>И вот тут на сцену выходит <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">DDD</code>. Центральная идея этого подхода заключается в том, что разработчики
постоянно активно сотрудничают с экспертами предметной области (со стороны заказчика) и вместе
с ними формируют так называемый единый язык. Этот язык будет использоваться для общения
между всеми членами команды, а позже отразится в исходном коде разрабатываемой программы.</p>
<p><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Ubiquitous Language</code> — это не бизнес-жаргон, навязанный разработчикам, а настоящий язык,
созданный целостной командой – экспертами в предметной области, разработчиками, бизнес-аналитиками
и всеми, кто вовлечён в создание системы. Роль в команде не столь существенна, поскольку каждый
член команды использует для описания проекта единый язык. Процесс создания единого языка более
творческий, чем формальный, так как он, как и любой другой естественный язык, постоянно развивается,
а те артефакты, которые вначале способствовали разработке полезного единого языка, со временем устаревают.
В итоге остаются только самые устойчивые и проверенные элементы.</p>
<p>Наиболее важное для разработчика – это умение слушать экспертов, получать максимальное количество полезных
знаний о предметной области. В то же время эксперты также должны прислушиваться к разработчикам и
их пожеланиям. Команда учится и растёт вместе, если она действует сплочённо, получая более
глубокое понимание бизнеса.</p>
<h2 id="heading-2-1">Bounded context</h2>
<p>Это второе по значимости свойство DDD после единого языка. Оба эти понятия взаимосвязаны и
не могут существовать друг без друга.</p>
<p>Итак, ограниченный контекст – это явная граница, внутри которой существует модель предметной области,
которая отображает единый язык в модель программного обеспечения.</p>
<p>В каждом ограниченном контексте существует только один единый язык.</p>
<ul>
<li>Ограниченные контексты являются относительно небольшими.</li>
<li>Ограниченный контекст достаточно велик только для единого языка изолированной предметной области, но не больше.</li>
<li>Единый значит «вездесущий» или «повсеместный», т. е. язык, на котором говорят члены команды
и на котором выражается отдельная модель предметной области, которую разрабатывает команда.</li>
<li>Язык является единым только в рамках команды, работающей над проектом в едином ограниченном контексте.</li>
<li>Попытка применить единый язык в рамках всего предприятия или что хуже, среди нескольких
предприятий, закончится провалом.</li>
</ul>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTE0NSwicHVyIjoiYmxvYl9pZCJ9fQ==--bdc50f9b2b41da0833fdaa3cdebdee4d6b6be1e2/bounded-context.png" alt="ddd" loading="lazy"/></p>
<p>Например, система биллинга крупной телекоммуникационной компании может иметь следующие
ключевые элементы (контексты):</p>
<ul>
<li>Клиентское обслуживание</li>
<li>Система безопасности и защиты</li>
<li>Резервное копирование</li>
<li>Взаимодействие с платёжными системами</li>
<li>Ведение отчётности</li>
<li>Система уведомлений</li>
</ul>
<p>В этом уроке были представлены самые базовые понятия и идеи предметно-ориентированного проектирования.
За более подробным описанием можно обратиться к <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://habrahabr.ru/post/316438/" rel="noopener noreferrer" target="_blank">отличной статье</a>
на Хабре, либо к книге Эрика Эванса. Также не могу не порекомендовать
<a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://www.youtube.com/watch?v=vkUTX1hruF8" rel="noopener noreferrer" target="_blank">моё выступление</a> на одной из региональных конференций,
в котором я рассказываю о похожих идеях, помогающих улучшить качество кода, который мы пишем.</p></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/js-domain-driven-design?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">2 месяца</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">DDD на Javascript</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите ООП и DDD для создания масштабируемого кода</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDAwNywicHVyIjoiYmxvYl9pZCJ9fQ==--f0b38f0e25ed59255acec6eaeaeec0a99aec453f/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Binary%20code-rafiki.png" alt="DDD на Javascript" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 900 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/js-ddd/lessons/domain/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 8</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/js-ddd/lessons/domain/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-Bukl1lYy.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>