RSS Telegram YouTube Apple Яндекс Spotify Amazon Почта

52. Mattermost

19.05.2024

Скачать

К списку выпусков

Ссылки выпуска:

Часто можно услышать вопрос про проект, web-сервис, из которого можно черпать идеи и на который можно ориентироваться. Сегодня поговорим как раз о таком. Важное пояснение, что мы не рекламируем этот проекта, а делимся интересными находками при чтении исходного кода.

Mattermost - это Slack, но с бесплатной версией с открытым исходым кодом. В бесплатной self-hosted версии доступны public/private каналы, общение 1 на 1, группы. Есть возможность звонить 1 на 1 с шерингом экрана. Платная версия улучшает авторизацию, позволяет групповые созвоны. Проект в открытом доступе с 2015 года.

Монорепозиторий

Код API-сервера (server), web-клиента (webapp), e2e тестов (e2e-tests), необходимых инструментов (tools), документации (api) находится в одном git-репозитории. Такой подход называется monorepo или монорепозиторий. Скажем, у вас есть API сервер и web-клиент и вместо того, чтобы разделить кодовую базу между двумя git-репозиториями, вы создаете один. При таком подходе удобно рефакторить проект. Стоит отметить, что проект пришел к монорепозиторию не сразу, а только год назад

Документация

Документация в проекте в формате OpenAPI, т.е. это YAML файл. И это очень большой файл, в котором больше 400 путей. Уложить это все в один файл неудобно: неочевидная навигация, большинство популярных IDE будут тормозить. Документацию разбили по файлам семантически, т.е. по смыслу. Так, есть файл files.yaml, в котором описаны все эндпоинты для манипуляции с файлами: загрузка, скачивание, получение ссылки для публичного доступа, превью файла и т.д. Или файл bots.yaml, описывающий методы для регистрации бота, его включения/отключения, конвертации бота в пользователя и т.д.

Генерация документа состоит из следующих шагов (описан в Makefile).

  1. Сборка единого YAML файла конкатенацией.
  2. Вставка примеров клиента на Go, для тех эндпоинтов, для которых определена модель в под-проекте server.
  3. Валидация полученного YAML файла (swgger-cli validate).
  4. Генерация итогового HTML файла документации с помощью redoc-cli.

Для работы с OpenAPIv3 здесь используется libopenapi.

libopenapi has full support for Swagger (OpenAPI 2), OpenAPI 3, and OpenAPI 3.1. It can handle the largest and most complex specifications you can think of.

Чтобы не читать объемную документацию, можно достаточно быстро ознакомиться с функционалом Mattermost прочитав по диагонали эту документацию.

End-to-end (e2e) тесты

Cypress - инструмент для тестирования web-приложений, всего что открывается в браузере. Проект взрослый, ему почти 10 лет и до сих пор это один из самых популярных инструментов в своей нише. Тесты пишутся на JavaScript и TypeScript. Запускаются как в браузере, так и в headless окружении ( отрисовка в Xvfb, X Virtual Framebuffer). Главный акцент проекта - удобство использования для разработчиков и тестировщиков. Во время тестирования можно визуально оценивать продвижение теста в браузере под управлением web-драйвера. А когда все готово, то есть готовые Docker образы для запуска тестов на ваших headless серверах, без реального отображения в браузерах. Когда смотрел исходники этого проекта нашел Microsoft Edge: для него есть deb пакет!

Постепенно проект переносит e2e тесты на Playwright. Playwright - это аналог cypress, но предоставляет API, используя которое можно писать проект не только на JS и TS, но и на Python, C# и Java.

webapp

Здесь я внимательно не копался. Веб-клиент написан на React, микс JavaScript и TypeScript.

server

Модели и API клиент содержатся в соответствующем пакете. Модели описывают объекты mattermost. mmctl, консольный клиент как раз использует этот пакет и соответствующие модели.

Каждая из моделей может реализовать интерфейс Auditable. Он нужен для сокрытия чувствительных данных объекта. Каждый раз когда в лог попадает такой объект, фильтруются чувствительные поля.

Миграции ведутся одновременно для PostgreSQL и для MySQL. Из примечательного, для текстовых полей в PostgreSQL используется VARCHAR, не TEXT. В миграциях активно используется PL/pgSQL. Также, используется тип массив, например, ids varchar(26)[]. Приятно было то, что миграции, как у меня в проектах, размещены в отдельной директории и это файлы с расширением sql. Все эти файлы включены в проект через embed механизм.

Для работы с PostgreSQL используется почти заброшенный pq.

Примечательно, что в проекте используется способ написания SQL-запросов, идентичный тому, что я недавно "придумал":

sql := `
    SELECT id, ...
    FROM table1 t1
    JOIN table2 ON ...
    WHERE ..

Логика приложения изолирована от СУБД через интерфейс, так что в MySQL и PostgreSQL таблицы именуются по-разному. В MySQL используется PascalCase (например, UserAccessTokens), в PostgreSQL snake_case (user_access_token).

Один большой интерфейс описывает работу с хранилищем, storage. И он имплементирован несколько раз:

Интересно, что почти везде 3 повтора при ошибке сериализации. Файл, имплементирующий это занимает 15000 строк кода. Апологеты DRY принципа должны быть в ярости.

В коде есть и "сырые" запросы к БД и запросы через squirrel старой версии (форк).

Т.е. каждый раз, когда тебе надо сходить в СУБД, ты должен по крайней мере написать три метода: запрос в СУБД, обработку на случай ошибки сериализации, трейсинг.

Помимо консольного клиента к приложению можно получить доступ и напрямую через API. Напомню, что mmctl как раз обращается к проекту через API. Для API выделен отдельный пакет api4. Примечательно, что более старые версии в коде отсутствуют, только api4. Возможно, проект эволюционировал и версии просто апали, возможно старые версии просто выпилили, несмотря на BC break. Однако, 4 версия API датирована 2017 годом, т.е. почти с момента публикации проекта.

Самый важный пакет, app содержит бизнес логику.

Работа с СУБД

Главный интерфейс Store (файл: server/channels/store/store.go) состоит из семантически сгруппированных методов, каждый из которых возвращает интерфейс, например, TeamStore:

type Store interface {
	Team() TeamStore
	Channel() ChannelStore
	Post() PostStore
	RetentionPolicy() RetentionPolicyStore
	Thread() ThreadStore
	User() UserStore
	Bot() BotStore
    // и еще 60+ сигнатур методов
}
    

Вот так выглядит TeamStore:

type TeamStore interface {
	Save(team *model.Team) (*model.Team, error)
	Update(team *model.Team) (*model.Team, error)
	Get(id string) (*model.Team, error)
	GetMany(ids []string) ([]*model.Team, error)
    // и еще 40+ сигнатур методов
}

Этот интерфейс реализован несколькими структурами, главная из которых SqlStore (здесь выполняются SQL-запросы). Также есть реализации интфейса Store для мока работы с СУБД в тестах, для трейсинг-обертки, для повтора выполнения запроса при ошибке сериализации транзакций в PostgreSQL или дедлоков в MySQL, для кеш обертки и т.д.

Выводы

Если вы хотите свой Slack, то гляньте на первый коммит mattermost: на старте он был очень компактным. По мере роста возникали потребности, повлекшие усложнение кода. Возможно, если вам нужен очень простой канал коммуникации, то mattermost будет для вас избыточным и ваша команда может написать казуально свой Slack за месяц-другой в качестве упражнения. А можно потратить месяц на то, чтобы покопаться в коде mattermost. В конце концов, это готовый проект с хорошо написанным кодом.

В тестах используется PostgreSQL, поэтому я бы выбрал эту СУБД для запуска своего экземпляра. Я планирую еще проверить работу Bleve для полнотекстового поиска, а не ElasticSearch и через месяц-два дам более полный отчет в этом подкасте чем это все закончилось.

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

К списку выпусков