Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Иван Матвеев. Как услышать бизнес и сделать быс...

Иван Матвеев. Как услышать бизнес и сделать быстро, в бюджет и качественно: уходим от feature-based разработки и концентрируемся на домене

Мы пишем код за деньги и решаем проблемы бизнеса, а значит бизнес-логика — важнейшая часть наших систем. Domain driven design (DDD) как раз об этом. Но часто DDD представляют как набор паттернов: aggregate root, repositories, ubiquitous language… Однако, чтобы строить приложение и делать хорошо для команды и бизнеса, необязательно все это применять. Я расскажу:
* о комбинации архитектурных практик, которые мы внедряем в Skyeng — и пути проб и ошибок, которым пришли в ним
* как мы определяем, что важно для бизнеса, и как гексагональная архитектура позволяет нам концентрироваться на домене
* и как мы постепенно переходим от feature based разработки к настоящему domain driven

Python Community Chelyabinsk

October 07, 2019
Tweet

More Decks by Python Community Chelyabinsk

Other Decks in Programming

Transcript

  1. SkyEng ✤ Skyeng — это онлайн-школа английского языка нового поколения.

    ✤ В школе работают профессионалы, помогающие жителям современных мегаполисов выучить английский язык в условиях недостатка времени.
  2. Маркетинг ✤ Ма ́ рке ́ тинг (от англ. marketing

    «рыночная деятельность») — организационная функция и совокупность процессов создания, продвижения и предоставления продукта или услуги покупателям и управление взаимоотношениями с ними с выгодой для организации.
  3. Маркетинг ✤ Тратить меньше (на продвижение и предоставление) ✤ Получать

    больше (увеличение аудитории, создание новых продуктов) ✤ Деньги!
  4. Задачка ✤ Надо сделать виджет ✤ Дизайн есть! ✤ Пользователь

    оставляет заявку ✤ В календаре выбирает дату и время вводного урока
  5. Задачка ✤ Надо сделать виджет ✤ Дизайн есть! ✤ Пользователь

    оставляет заявку ✤ В календаре выбирает дату и время вводного урока
  6. Как будем делать тех. ревью? ✤ От базы/интеграций ✤ От

    api фронтенда ✤ От проблемы бизнеса
  7. И от базы/интеграций и от api ✤ Сервис букинга —

    получить, выбрать, отменить ✤ Фронтовое api — получить, выбрать, отменить ✤ База — таблица свзяка education_service_id, booking_slot_id ✤ Что там дальше, подумаем потом ;) ✤ Что думает по этому поводу бизнес?
  8. Чего хочет бизнес от разработки. ✤ Качественно — не терять

    сценарии и лучше понимать бизнес проблемы ✤ Быстро — быстро описывать сценарии в коде в отрыве от конкретных технологий (технологии не важны) ✤ Дешево — возможность проверять сценарии как можно раньше (оно вообще работает? может и разрабатывать не надо?)
  9. Что дальше? ✤ Опишем сервис ✤ Пробуем удовлетворить все 3

    потребности ✤ Посмотрим со стороны гексагональной архитектуры
  10. «Allow an application to equally be driven by users, programs,

    automated test or batch scripts, and to be developed and tested in isolation from its eventual run- time devices and databases.». — Alistair Cockburn Позволяет взаимодействовать с приложением как пользователю, так и программам, автоматическим тестам, скриптам пакетной обработки. Также позволяет разрабатывать и тестировать приложение без каких-либо дополнительных устройств или баз данных.
  11. Hexagonal architecture / Ports and adapters Пользователь
 Программы
 Тесты
 Скрипты


    Базы
 Внешние Апи
 Доп. устройства
 Приложение
  12. ✤ Какие сервисы нужны? 
 (что если их нет? или

    есть?) ✤ Что нам понадобится от них? ✤ Что нужно от репозитория?
  13. class SelfTrialBookingService { ... public function __construct( OperatorsServiceInterface $operatorsService, BookingServiceInterface

    $bookingService, SelfTrialRepositoryInterface $selfTrialRepository ) { $this->operatorsService = $operatorsService; $this->bookingService = $bookingService; $this->selfTrialRepository = $selfTrialRepository; } } ✤ Какие сервисы нужны? 
 (что если их нет? или есть?) ✤ Что нам понадобится от них? ✤ Что нужно от репозитория?
  14. interface OperatorsServiceInterface { public function holdCall(int $educationServiceId, DateInterval $interval, string

    $reason): void; public function disableCall(int $educationServiceId, string $reason): void; public function enableCall(int $educationServiceId, string $reason): void; } ✤ Отложить звонок, сразу после заявки ✤ Отменить звонок вообще, если пользователь выбрал дату/время ✤ Назначить звонок, если пользователь передумал
  15. interface BookingServiceInterface { public function bookSlot(string $slotId, string $reason): void;

    public function cancelSlot(string $slotId, string $reason): void; public function getAvailableSlots(); } ✤ Выбрать дату/время ✤ Отменить дату/время ✤ Получить список дат/времени
  16. interface SelfTrialRepositoryInterface { public function save(SelfTrial $selfTrial): void; public function

    getSelfTrialByEducationServiceId(int $educationServiceId): ?SelfTrial; } ✤ Сохранить ✤ Получить
  17. public function startSelfTrialProcess(int $educationServiceId): void { $this->operatorsService->holdCall( $educationServiceId, new DateInterval(self::HOLD_CALL_INTERVAL),

    'self_trial' ); $selfTrial = SelfTrial::start($educationServiceId); $this->selfTrialRepository->save($selfTrial); } public static function start(int $educationServiceId) : SelfTrial { $instance = new self($educationServiceId); $instance->status = SelfTrialStatus::STARTED(); return $instance; }
  18. public function bookSlot(int $educationServiceId, string $slotId, string $reason): void {

    $selfTrial = $this ->selfTrialRepository ->getSelfTrialByEducationServiceId($educationServiceId); //Валидация и DomainException $this->bookingService->bookSlot($slotId, $reason); $this->operatorsService->disableCall($educationServiceId, $reason); $selfTrial->bookSlot($slotId); $this->selfTrialRepository->save($selfTrial); } public function bookSlot(string $slotId) { $this->slotId = $slotId; $this->status = SelfTrialStatus::BOOKED(); }
  19. public function cancelSelfTrial(int $educationServiceId, string $reason): void { $selfTrial =

    $this ->selfTrialRepository ->getSelfTrialByEducationServiceId($educationServiceId); //Валидация и DomainException $this->bookingService->cancelSlot($selfTrial->getSlotId(), $reason); $this->operatorsService->enableCall($educationServiceId, $reason); $selfTrial->cancel(); $this->selfTrialRepository->save($selfTrial); } public function cancel() { $this->status = SelfTrialStatus::CANCELLED(); }
  20. Application ✤ Качественно — не терять сценарии и лучше понимать

    бизнес проблемы ✤ Быстро — быстро описывать сценарии в коде в отрыве от конкретных технологий (технологии не важны) ✤ Дешево — возможность проверять сценарии как можно раньше (оно вообще работает? может и разрабатывать не надо?)
  21. public function bookSlot(int $educationServiceId, string $slotId, string $reason): void {

    $selfTrial = $this ->selfTrialRepository ->getSelfTrialByEducationServiceId($educationServiceId); //Валидация и DomainException $this->bookingService->bookSlot($slotId, $reason); $this->operatorsService->disableCall($educationServiceId, $reason); $this->operatorsService->enableCall($educationServiceId, $reason); $selfTrial->bookSlot($slotId); $this->selfTrialRepository->save($selfTrial); }
  22. При чем тут DDD? ✤ Разработка от предметной области ✤

    Ubiquitous Language (единый язык) ✤ Можно показать заказчику ✤ Если не поймет, покрыть BDD тестом ✤ Bounded Context (контекст предметной области)
  23. Hexagonal architecture / Ports and adapters Пользователь
 Программы
 Тесты
 Скрипты


    Базы
 Внешние Апи
 Доп. устройства
 Приложение
  24. $bookingService = new SelfTrialBookingService( new OperatorsServiceStupidMock(), new BookingServiceStupidMock(), new SelfTrialRepositoryStupidMock()

    ); $bookingService->setLogger(new \Psr\Log\NullLogger()); echo 'Starting self trial' . PHP_EOL; $bookingService->startSelfTrialProcess(1); echo '[:::::::::::::::::::::::::]' . PHP_EOL; echo 'Book slot' . PHP_EOL; $bookingService->bookSlot(1, 'slot-1', 'api_call_from_user_book'); echo '[:::::::::::::::::::::::::]' . PHP_EOL; echo 'Cancel slot' . PHP_EOL; $bookingService->cancelSelfTrial(2, 'api_call_from_user_cancel');
  25. Starting self trial OperatorsServiceStupidMock: Call hold for 1 due to

    self_trial SelfTrialRepositoryStupidMock: Saved SelfTrial for 1 [:::::::::::::::::::::::::] Book slot SelfTrialRepositoryStupidMock: Found SelfTrial for 1 BookingServiceStupidMock: Slot slot-1 booked for 1 due to api_call_from_user_book OperatorsServiceStupidMock: Call disabled for 1 due to api_call_from_user_book SelfTrialRepositoryStupidMock: Saved SelfTrial for 1 [:::::::::::::::::::::::::] Cancel slot SelfTrialRepositoryStupidMock: Found SelfTrial for 2 BookingServiceStupidMock: Slot slot-2 cancelled due to api_call_from_user_cancel OperatorsServiceStupidMock: Call enabled for 2 due to api_call_from_user_cancel SelfTrialRepositoryStupidMock: Saved SelfTrial for 1
  26. Зачем ✤ Позволяет концентрироваться на домене ✤ Выделение бизнес логики

    ✤ Тесты ✤ Заменяемые элементы ✤ Дисциплина (именование папок, куда что положить)
  27. Заметки на полях ✤ Что с атомарностю (@synchronized)? ✤ Почему

    не Symfony Workflow ✤ Рисуем Сову? ✤ Почему не ивенты из модели? ✤ Где эксепшены? ✤ Почему так много кода?