Вступление
===
<i class="fa fa-file-text"></i> **MakeMeBeauty** это агрегатор в сфере женской красоты, направленный на сбор и обработку данных докторов и клиник пластической хирургии и косметологии. Приоритетная задача проекта это выдача посетителям полной информации о докторах, фотографии их работ, отзывы, возможность задать им вопросы и определиться с выбором нужного доктора.
Сервис задумывался как потенциально **нагруженная платформа**, по этому проделана значительная работа по поддержанию **единого код-стайла**, управления сложность, **гибкость** и **легкостью** к масштабированию системы.
Данная документация содержит по большей части описание элементов системы которые не столь очевидны или необходимы для знания для развития проекта.
Используемые технологии
===
**Инфраструктура**
<i class="fa fa-toggle-on fa-fw"></i> Сервер приложения: 1шт, Docker Swarm.
<i class="fa fa-toggle-on fa-fw"></i> Сервер для логов ELK: 1шт, Docker Swarm.
<i class="fa fa-toggle-on fa-fw"></i> Deploy: Jenkins, пайплайны деплоя в приложении.
**Приложение**
<i class="fa fa-eye fa-fw"></i> Фреймворк: Laravel (custom).
<i class="fa fa-eye fa-fw"></i> Шаблонизатор: Blade.
<i class="fa fa-eye fa-fw"></i> ORM: Eloquent.
<i class="fa fa-eye fa-fw"></i> Архитектура: Модульная, Луковая.
<i class="fa fa-eye fa-fw"></i> Покрытие тестами: крайне низкое
**Серверные технологии**
<i class="fa fa-toggle-on fa-fw"></i> Язык: PHP 7.2.8.
<i class="fa fa-toggle-on fa-fw"></i> База данных: MySQL 8.0.
<i class="fa fa-toggle-on fa-fw"></i> Веб-сервер: Nginx.
<i class="fa fa-toggle-on fa-fw"></i> Кэширование: Memcached.
<i class="fa fa-toggle-on fa-fw"></i> Локальная контейнеризация: Docker Compose.
Как запустить приложение локально:
===
Локальная разработка приложения, как указано выше ведется на Docker Compose, для запуска приложения нужно иметь установленный докер, и в терминале ввести:
```sh=
docker-compose up -d --build
```
После первого запуска, --build можно убрать из строки.
Для того что бы войти внуть контейнера приложения нужно ввести:
```sh=
docker exec -it mmb-app bash
```
> Дисклеймер: разработка велась на маке и линуксе, на windows есть вероятность другого поведения при сборке Dockerfile
Следует учитывать, что в приложении локальный php отличается от php на продакшене.
| Файл сборки | Где используеся | Чем отличается |
| ----------------------------- | --------------- | -------------- |
| /docker/local/app/Dockerfile | Локально | ✔ xDebug |
| /docker/base/php/Dockerfile | На продакшене | ✖ xDebug |
Так же очень важно что сборка php на продакшене происходит на сервисе [hub.docker.com](https://hub.docker.com/) по вызову хука при обновлении мастер ветки и происходит не мгновенно. По этому при обновлении кода относщегося к PHP, не следует сразу в Jenkins запускать деплой, лучше дождаться окончания сборки.
**Структура локально развертывания**
```graphviz
digraph hierarchy {
nodesep=0.3 // increases the separation between nodes
node [color=Blue,fontname=Courier,shape=box]
edge [color=Blue, style=dashed] //All the lines look like this
DockerCompose->{
Nginx
App
Filebeat
MySql
Mailcatcher
Memcached
}
}
```
| Контейнер | Описание |
| -------------| --------------- |
| Nginx | Веб сервер, написана структура логирования под Filebeat |
| App | Приложение |
| Filebeat | Демон которые обрабатывает логи приложения, и сбрасывает их на ELK сервер. Если ELK сервер выключен, контейнер будет отваливаться, но при включении ELK все равно отправит логи |
| MySql | База данных |
| Mailcatcher | Локальный mail client, нужен что бы отлавливать и дебажить email сообщения |
| Memcached | Кэширование, используется на пользовательских view |
**Структура продакшен развертывания**
```graphviz
digraph hierarchy {
nodesep=0.3 // increases the separation between nodes
node [color=Blue,fontname=Courier,shape=box]
edge [color=Blue, style=dashed] //All the lines look like this
DockerCompose->{
Nginx
App
Filebeat
MySql
Memcached
Supervisor
}
}
```
| Контейнер | Описание |
| -------------| --------------- |
| Nginx | Веб сервер, написана структура логирования под Filebeat |
| App | Приложение |
| Filebeat | Демон которые обрабатывает логи приложения, и сбрасывает их на ELK сервер |
| MySql | База данных |
| Memcached | Кэширование, используется на пользовательских view |
| Supervisor | Запускает crontab и очереди. В перспективе тут должен быть RabbitMQ |
Как деплоить на продакшен?
===
http://exchangeorder.ru:8084

Иногда Jenkins может упасть по памяти, или начать тупить и долго собирать приложение, в этом случае следует его перезапустить, и на энное количество билдов это помогает, предполагаемая причина - слабый сервер
Используемые внешние сервис и инструменты:
===
| Контейнер | Описание |
| -------------| --------------- |
| Voximplant | Телефония, колл-центр, очереди в коллцентр. |
| Mail Cloud Solution Объектное хранилище | Облачное хранилище данных. |
| Google geolocation | Получение геоданных |
| api.hh.ru/metro | Получение станций метро |
**Voximplant - [voximplant.ru](https://voximplant.ru)**
Сервис облачно телефонии. В нем покупается вирутальный номер телефона. Внутри MakeMeBeaut алиаситься виртуальный с реальным и настроены скрипты на редирект звонка с виртуального на реально, но плюс к этому мы получаем запись разговора и возможность им управлять.
Так же есть сценарий колл-центра, т.е полноценная очередь звонков, музыка при прослушивании. Минимальный web интерфейс.
**Voximplant - [mcs.mail.ru](https://mcs.mail.ru/storage-old/)**
Сервис облачного хранилища данных по типу Amazon S3, умеет в приватные и временные файлы. Быстро их отдает. Используется в системе в виде FileStorageInterface.
**Google geolocation**
Сервис предназначен для получения данных о расстоянии до ментро пешком и по прямой, а так же получения gps координат адресов.
**HeadHanter - api.hh.ru/metro**
Api для получения списка станций метро с цветами их веток
Особенности архитектуры:
===
Основные элементы архитектуры системы:
- Луковая архитертура
- Компоненты
- Manager, Service
- Модули
- Ui
- Dto, ValueObject
**Компоненты** не завязываются на бизнес-логику приложения, служат для того что бы сохранять/отдавать данные и хранить их структуру в бд.
Компоненты не отдают Eloquent модели, не отдают массивы данных. Только Dto, Коллекции, списки Dto или в редких случаях скалярные данные
**Manager** решает пробелму Eloquent с паттерном AR в Laravel. В данном приложении, работа с Eloquent моделями происходит только внутри Manager, которые в основном наследуются от родительского менеджера, пример в существующих приложениях вполне жизнеспособен. Возможно есть решения интереснее, но нынешнее не считая небольшого бойлерплейта вполне покрывает эту проблему.
**Service** предназначены для получения данных из менеджеров с фильтрами и заворачивание этого в своеобразные Dto или Коллекции.
**Модули** содержат контроллеры, роуты уже завязанные на бизнес логику приложения. В своей логике используют компоненты приложения. Предназначены для отдачи ответов REST API приложения. Ответ эндпоинта рекомендуется возвращать использую Formatter по типу уже написанных модулей.
**Ui** слой приложения предназначен для отдачи пользовательского интрфейса в браузере, не важно, будь это админ интерфейс или клиентский. Содержет в себе view темплейты на blade которые генерируют страницу на основе данных из дочерних классов PageData. При написании следуют учитывать существущий стиль, не усложняя общую сложность проекта новыми структурами
**Dto, ValueObject** важные паттерны для приложения. Разработчику работает над проектом, обязательно надо понимать их, и использовать их в приложении.
Отличия от Laravel из коробки
===
## Миграции:
Учитывая то что у нас используется модульная архитектура, мы должны писать миграции конкретно под модуль внутри него, так же внутри каждого модуля должна быть своя история миграций. Для решения этого вопроса мы модифицировали команды миграций Laravel, которые теперь принимают последним аргументов названием модуля или "all" для выполнения команды над всеми модулями.
Пример:
| Было | Стало |
| ------------------------ | --------------- |
| php artisan migrate | php artisan migrate all |
| php artisan migrate | php artisan migrate User |
| php artisan migrate | php artisan migrate Doctor |
| php artisan migrate:fresh | php artisan migrate:fresh all |
| php artisan migrate:fresh | php artisan migrate:fresh Doctor |
| php artisan migrate:rollback | php artisan migrate:rollback Doctor |
В таком же стиле все аналогично работает с другими командами миграции Laravel

## Вертикальный шардинг БД
Изначально в системе задуман горизонтальный шардинг баз данных. Сделано это для разделения баз по компонетом. Т.е в задумке компонент User будет использовать базу данных User, Telephony использовать Telephony и т.д. Данное решение возможно преждевременно, но оно позволяет укладываться в модель компонентов.
При разработке нового компонента, например Telephony, если он использует бд, нужно добавить команды на создание баз данных в локальном окружении в файлик
```sh=
/docker/local/mysql/db.sql
```
Две строки
```sh=
CREATE DATABASE IF NOT EXISTS `mmb_telephony`;
CREATE DATABASE IF NOT EXISTS `test_mmb_telephony`;
```
и так же в файл
```sh=
/src/app/component/core/database/ConnectionResolverInterface.php
```
который содержит данные о используемых данных так же нужно добавить строку о новой базе данных:
```sh=
public const TELEPHONY_CONNECTION_NAME = 'Telephony';
```
Далее уже внутри компонента, нужно лишь у менеджера указать используемый коннект из этого интерфейса.
Кэширование
===
Сейчас в системе кэширование идет на уровне возвращаемых View и коллекций объектов которые нужны для генерации практически всех объектов. Так же кэширует пробег по списку модулей и их регистрация, но реализация последнего компонента все равно имеет недостатки. Имеется надстройка над стандартным кэшированием Laravel, найти ее можно по адресу:
```sh=
/src/app/core/cache/Cache.php
```
В ней находиться логика генерации кэш-ключей, так же по классу можно найти места использования кэша, и найти там код отвественный за сборс кэша. Но если совсем просто о сбросе, то к любому урлу нужно добавить параметр **?cache** . Например
урл *localhost/catalog* превратить в *localhost/catalog?cache*, и тогда сбросятся все элементы кэширования, используемые на этой странице.
Или просто заного задеплоить приложение, и весь кэш memcached сброситься.
Описание компонентов и модулей
===
Часть модулей делалась в момент недостаточной проработки системы, по этому нужно учитывать, что они могут в некоторых моментах несоотвествовать требованиям.
**Application**
- Заявки на запись: в ui еще не реализовано
**Article**
- Все связанное с блогов: в ui еще не реализовано
- Вопросы ответы: в ui еще не реализовано
**Clinic**
- Сертификаты клиники: в ui еще нигде не реализовано
- Сети клиник
- Фото клиник: в ui еще не реализовано
**Doctor**
- Все связанное с доктором
- Профиль
- Награды: в ui еще не реализовано
- Связь с специализацией
- Фото до после (before_after): в ui еще не реализовано
**FileStorage**
- Все для хранения файлов в файловом хранилище
**Geolocation**
- Адреса
- Города
- Дистанция до метро
- Районы
- Округа
- Метро
**Page**
- Типовые страницы
- Редиректы
- Блоки на сайте
- Лейауты страниц
- Шаблоны мета тегов
**Review**
- Отзывы: в ui еще не реализовано
- Фильтр стоп-слов: в ui еще не реализовано
**Specialization**
- Специализации
- Заболевания
- Услуги
- Профили работ
- Стоимость услуг
- Связи между элементами выше
**TaskManager**
- Компонент для внешнего сервиса задач, нужен что бы ставить задачи например во Wrike, конкретный адаптер под Wrike уже назначается через ServiceProvider
**Telephony**
- Клиники на показать номер
- Вирутальные номера
- История звонков
- Колл-центр
- Связь вирутально с реальный
Компонент не до конца доработан, в конечно итоге подразумевается что он еще должен уметь создавать номера в внешнем сервисе номеров, и тянуть все данные оттуда
**User**
- Пользователи и связанное с ними
- Аутентификация
Сервис ClinicUser временно выключен, но в конечном итоге приложения должно быть 3 типа пользователей: админы, клиники, и пациенты
Админ панелью
===
Админ панель кастомная, но реализовано на ней множество функций, в них нужно вникнуть, дабы не писать велосипеды. Следуется внимательно изучить уже имеющиеся контроллеры, они лежат в папке:
```sh=
/src/app/ui/admin/controller
```
Продуман легкий вывод табличных данных c помощью **entityListBuilder**,
простое создание форм редактирования и создания сущностей использую **AddFormBuilder** и **EditFormBuilder**. Безусловно в некоторых случаях его можно будет расширять, но перед этим нужно посмотреть на интерфейс
```sh=
/src/app/ui/admin/builder/form/field/FieldTypeEnum
```
Уже написаны реализации на множество ситуаций, включая связи many to many, запросы к ajax фильтрации и прочее.
**Удачи, она пригодиться :smile:**