Disclaimer

Данный проект рассматривается как pet-project. Любую критику и советы готов увидеть в комментариях.

Репозиторий с исходным кодом: https://github.com/theartofdevel/notes_system

Под катом текстовая расшифровка с картинками.

Техническое задание

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

Основные возможности:

  • регистрация и авторизация в системе,

  • создание/изменение/удаление заметок,

  • прикрепление файла,

  • категоризация и тегирование заметок,

  • полнотекстовый поиск.

Сделаем версию под браузер, приложения на Android и iOS могут появиться в будущем.

Основные страницы UI:

  • авторизация,

  • регистрация,

  • список заметок,

  • карточка заметки.

Заметки должны поддерживать формат Markdown и состоять из заголовка и тела. В ТЗ намеренно нет нефункциональных требований, таких как производительность, надёжность, катастрофоустойчивость и так далее, чтобы не усложнять систему.

Проектирование системы

Итак, функциональность нам известна, давайте проектировать. Начнём с выбора инфраструктуры.

CI/CD

Собирать систему и деплоить код будем при помощи CircleCI или GitHub Actions. Все сервисы будем упаковывать в Docker-контейнеры и запускать при помощи Docker-compose.​

Система контроля версий

Уже есть репозиторий на GitHub. Он будет использоваться как монорепозиторий. Это ускорит разработку и уменьшит количество дополнительной работы с гитом. Весь код будет в свободном доступе под лицензией GPLv3.

Zipkin

Нам нужно дебажить сетевое взаимодействие между сервисами, поэтому мы используем стандарт Opentracing и в качестве реализации возьмем Zipkin. У него много готовых библиотек и для Python, и для Golang.

Search and filter

Для полнотекстового поиска и фильтрации мы возьмём стек ELK, а именно Elasticsearch и Logstash. На данном этапе это как стрелять из пушки по воробьям, вполне можно было организовать этот функционал на хранилище, но:

  • в качестве обучения и демонстрации возможностей микросервисов это будет отличным примером;

  • если сервисом начнут пользоваться люди, то мы уже подумали о масштабировании и производительности.

Для наполнения Elasticsearch данными будем настраивать ETL-процессы и использовать Apache Airflow.

Logs

Так как у нас уже есть Elasticsearch, мы будем туда заливать логи всех сервисов. Чтобы отслеживать пересечения логов бизнес-процессов по разным сервисам, будем добавлять в лог TraceId, который нам останется от Zipkin. Логи будем собирать агентом Telegraf или FileBeat.

Service Discovering and Configuration

Для Service Discovering используем Consul и его DNS-сервер. Логика следующая: сервис обращается к другому сервису по хостнейму, а резолвить хостнейм будет Consul.

Для конфигурации всех сервисов будем использовать встроенное в Consul key-value-хранилище. Возьмём утилиту Consul Template. Она будет следить за файлом шаблона и генерировать конфигурацию сервиса, которую он будет использовать при старте, а при изменении значений в Consul Consul Template будет рестартовать сервис.

Сервисы

Теперь посмотрим, какие сервисы у нас будут.

NoteService

Сервис с бизнес-логикой основной сущности. Реализуем основные операции: получение, создание, обновление и удаление. Использовать будем Golang, REST API, формат данных — JSON.

У сервиса будет своё хранилище. Возьмем документоориентированную MongoDB. Она легко масштабируется, формат данных — документ в виде JSON плюс шардирование «из коробки» — опять же пригодится, если будет нагрузка.

FileService

Для управления файлами заметок сделаем отдельный сервис. Скорее всего, это будет совместимое с S3 хранилище MinIO. Файлы будем хранить на файловой системе.

CategoryService

Для управления категориями сделаем отдельный сервис со своим хранилищем. Категории будем хранить в древовидной структуре, поэтому возьмем Neo4j — графовую базу данных. Её основная особенность в том, что коннекты между данными хранятся, а не вычисляются во время запроса. Также она использует язык запросов Cypher, который гораздо проще, чем SQL для написания запросов с неограниченной вложенностью.

Сервис будем делать на языке Python 3 и фреймворке Flask. Асинхронность здесь не так важна, к тому же запускать приложение мы будем при помощи Gunicorn, который сможет распараллелить инстансы сервиса.

APIService

Мы будем делать только веб-приложение без мобильных клиентов, но так как в будущем они могут появиться, нам нужен отдельный сервис с API.

API у нас по большей степени простой CRUD, различного функционала немного, и весь он вращается вокруг заметок. Основных сущностей также мало, поэтому будем готовить обычный REST API.

SearchService

Для полнотекстового поиска сделаем SearchService. Именно он будет обладать доступом в Elasticsearch. Работать с Elasticsearch проще на Python, поэтому выбор очевиден.

UserService

Для пользователей системы будет отдельный сервис также с REST API и хранилищем PostgreSQL.

WebApplication

Для создания веб-приложения используем Vue.js или React.js.

В итоге мы получили такую архитектуру:

Архитектура нашей системы
Архитектура нашей системы

Если в системе появятся асинхронные операции, например, отчёты, то мы легко внедрим в эту архитектуру очереди событий.

В следующей части мы начнём разработку системы. Будут показаны основные моменты процесса разработки и конфигурации используемых продуктов.

Комментарии (6)


  1. dandykry
    08.10.2021 17:07
    +4

    Мне пока ничего непонятно, но ты продолжай!


  1. korsetlr473
    10.10.2021 14:28

    Тоже не понял как всё это будет собираться ?

    Если мне нужно на странице конкретного Каталога , сделать поиск по слову в тексте и отсортировать по файлам ?

    это запрос с 4м сервисам и джойнами данный в одной функции на бэкэнде ?


    1. dikkini Автор
      10.10.2021 14:51

      всю фильтрацию и поиск по заметкам предполагается делать в ElasticSearch


      1. korsetlr473
        10.10.2021 19:00
        +2

        в чем тогда смысл сервисов? если у вас в эластике лежит агрегированная полная информация?

        тогда просто любой запрос можно сразу по эластику проводить.

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


        1. dikkini Автор
          10.10.2021 19:24

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


  1. SubarYan
    11.10.2021 12:52

    Одной MongoDB было бы достаточно. На MongoDB можно и поиск сделать, а такжи и графы хранить.