Исходный код Зависимости и проекты-зонтики
В этой главе мы обсудим, как управлять зависимостями в Mix.
Наш kv приложение завершено, поэтому пришло время реализовать сервер, который будет обрабатывать запросы, определённые в первой главе:
CREATE shopping OK PUT shopping milk 1 OK PUT shopping eggs 3 OK GET shopping milk 1 OK DELETE shopping eggs OK
Однако вместо добавления большего кода в kv приложение, мы собираемся создать TCP-сервер как другое приложение, которое является клиентом kv приложение. Поскольку весь рантайм и экосистема Elixir ориентированы на приложения, имеет смысл разделить наши проекты на более мелкие приложения, которые работают вместе, а не создавать одно большое монолитное приложение.
Перед созданием нового приложения мы должны обсудить, как Mix обрабатывает зависимости. На практике мы обычно работаем с двумя типами зависимостей: внутренними и внешними. Mix поддерживает механизмы работы с обеими.
Внешние зависимости
Внешние зависимости — это те, которые не связаны с вашей предметной областью. Например, если вам нужен HTTP-API для вашего распределённого приложения KV, вы можете использовать проект Plug в качестве внешней зависимости.
Установка внешних зависимостей проста. Чаще всего мы используем менеджер пакетов Hex, перечислив зависимость в функции deps в нашем mix.exs файле:
def deps do
[{:plug, "~> 1.0"}]
end
Эта зависимость относится к последней версии Plug в серии версий 1.x.x, которая была добавлена в Hex. Это обозначается префиксом ~> перед номером версии. Дополнительную информацию о задании требований к версиям см. в документации для модуля Version.
Обычно стабильные релизы добавляются в Hex. Если вы хотите зависеть от внешней зависимости, которая всё ещё находится в разработке, Mix может управлять зависимостями Git:
def deps do
[{:plug, git: "https://github.com/elixir-lang/plug.git"}]
end
Вы заметите, что при добавлении зависимости в ваш проект Mix генерирует mix.lock файл, который гарантирует повторяемость сборки. Файл блокировки должен быть добавлен в вашу систему управления версиями, чтобы гарантировать, что все, кто использует проект, будут использовать те же версии зависимостей, что и вы.
Mix предоставляет множество задач для работы с зависимостями, которые можно найти в mix help:
$ mix help mix deps # Lists dependencies and their status mix deps.clean # Deletes the given dependencies' files mix deps.compile # Compiles dependencies mix deps.get # Gets all out of date dependencies mix deps.tree # Prints the dependency tree mix deps.unlock # Unlocks the given dependencies mix deps.update # Updates the given dependencies
Наиболее распространёнными задачами являются mix deps.get и mix deps.update. После получения зависимости они автоматически компилируются для вас. Вы можете узнать больше о deps, набрав mix help deps, и в документации для модуля Mix.Tasks.Deps.
Внутренние зависимости
Внутренние зависимости — это те, которые специфичны для вашего проекта. Они обычно не имеют смысла вне контекста вашего проекта/компании/организации. Часто вы хотите сохранить их конфиденциальными, по техническим, экономическим или деловым причинам.
Если у вас есть внутренняя зависимость, Mix поддерживает два метода работы с ними: репозитории Git или проекты-зонтики.
Например, если вы поместите проект kv в репозиторий Git, вам необходимо добавить его в код deps, чтобы использовать его:
def deps do
[{:kv, git: "https://github.com/YOUR_ACCOUNT/kv.git"}]
end
Однако, если репозиторий приватный, вам может потребоваться указать приватный URL git@github.com:YOUR_ACCOUNT/kv.git. В любом случае Mix сможет его получить, если у вас есть соответствующие учётные данные.
Использование репозиториев Git для внутренних зависимостей в Elixir несколько не рекомендуется. Помните, что рантайм и экосистема Elixir уже предоставляют концепцию приложений. Таким образом, ожидается, что вы часто будете разбивать свой код на приложения, которые могут быть логически организованы, даже в рамках одного проекта.
Однако, если вы поместите каждое приложение как отдельный проект в репозиторий Git, ваши проекты могут стать очень сложными для поддержки, так как вы будете тратить много времени на управление этими репозиториями Git, а не на написание кода.
По этой причине Mix поддерживает «проекты-зонтики». Проекты-зонтики используются для создания приложений, которые работают вместе в одном репозитории. Именно этот стиль мы собираемся изучить в следующих разделах.
Давайте создадим новый проект Mix. Мы творчески назовём его kv_umbrella, и этот новый проект будет содержать существующее kv приложение и новое kv_server приложение внутри. Структура каталогов будет выглядеть так:
+ kv_umbrella
+ apps
+ kv
+ kv_server
Интересно, что Mix имеет множество удобств для работы с такими проектами, например, возможность компилировать и тестировать все приложения внутри apps одной командой. Однако, даже если все они перечислены вместе внутри apps, они всё ещё декомпозированы друг от друга, поэтому вы можете собирать, тестировать и развертывать каждое приложение по отдельности, если захотите.
Итак, приступим!
Проекты-зонтики
Начнём новый проект с помощью mix new. Этот новый проект будет называться kv_umbrella и нам нужно передать опцию --umbrella при его создании. Не создавайте этот новый проект внутри существующего kv проекта!
$ mix new kv_umbrella --umbrella * creating README.md * creating .formatter.exs * creating .gitignore * creating mix.exs * creating apps * creating config * creating config/config.exs
Из выведенной информации можно увидеть, что генерируется гораздо меньше файлов. Сгенерированный mix.exs файл тоже отличается. Давайте посмотрим (комментарии удалены):
defmodule KvUmbrella.MixProject do
use Mix.Project
def project do
[
apps_path: "apps",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
defp deps do
[]
end
end
Что отличает этот проект от предыдущего, так это запись apps_path: "apps" в определении проекта. Это означает, что этот проект будет работать как зонтик. Такие проекты не имеют исходных файлов ни тестов, хотя они могут иметь свои собственные зависимости. Каждое дочернее приложение должно быть определено внутри каталога apps.
Давайте перейдём в каталог apps и начнём создавать kv_server. На этот раз мы собираемся передать флаг --sup , который скажет Mix сгенерировать древовидную структуру для контроля параллельных задач автоматически, вместо того, чтобы создавать её вручную, как мы делали в предыдущих главах:
$ cd kv_umbrella/apps $ mix new kv_server --module KVServer --sup
Сгенерированные файлы похожи на те, что мы сгенерировали для kv, с некоторыми отличиями. Давайте откроем mix.exs:
defmodule KVServer.MixProject do
use Mix.Project
def project do
[
app: :kv_server,
version: "0.1.0",
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",
elixir: "~> 1.14",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications
def application do
[
extra_applications: [:logger],
mod: {KVServer.Application, []}
]
end
# Run "mix help deps" to learn about dependencies
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
# {:sibling_app_in_umbrella, in_umbrella: true},
]
end
end
Во-первых, так как мы сгенерировали этот проект внутри kv_umbrella/apps, Mix автоматически определил структуру зонтика и добавил четыре строки в определение проекта:
build_path: "../../_build", config_path: "../../config/config.exs", deps_path: "../../deps", lockfile: "../../mix.lock",
Эти опции означают, что все зависимости будут извлечены в kv_umbrella/deps, и они будут совместно использовать одни и те же файлы сборки, конфигурации и блокировки. Мы ещё не говорили о конфигурации, но из этого можно сделать вывод, что все конфигурации и зависимости совместно используются всеми проектами в зонтике, а не по отдельным приложениям.
Второе изменение — в функции application внутри mix.exs:
def application do
[
extra_applications: [:logger],
mod: {KVServer.Application, []}
]
end
Поскольку мы передали флаг --sup , Mix автоматически добавил mod: {KVServer.Application, []}, указав, что KVServer.Application — это модуль обратного вызова нашего приложения. KVServer.Application запустит древовидную структуру контроля параллельных задач нашего приложения.
Фактически, давайте откроем lib/kv_server/application.ex:
defmodule KVServer.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
@impl true
def start(_type, _args) do
# List all child processes to be supervised
children = [
# Starts a worker by calling: KVServer.Worker.start_link(arg)
# {KVServer.Worker, arg},
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: KVServer.Supervisor]
Supervisor.start_link(children, opts)
end
end
Обратите внимание, что он определяет функцию обратного вызова приложения, start/2, и вместо определения надсмотрщика под названием KVServer.Supervisor , использующего модуль Supervisor, он удобно определил надсмотрщика внутри! Вы можете узнать больше о таких надсмотрщиках, прочитав документацию модуля Supervisor.
Мы уже можем попробовать наше первое дочернее приложение зонтика. Мы могли бы запустить тесты внутри каталога apps/kv_server , но это не очень интересно. Вместо этого перейдите в корень проекта зонтика и запустите mix test:
$ mix test
И это работает!
Поскольку мы хотим, чтобы kv_server в конечном итоге использовал функциональность, которую мы определили в kv, нам нужно добавить kv в качестве зависимости к нашему приложению.
Зависимости внутри проекта-зонтика
Зависимости между приложениями в проекте-зонтике всё ещё должны быть явно определены, и Mix делает это легко. Откройте apps/kv_server/mix.exs и измените функцию deps/0 на следующее:
defp deps do
[{:kv, in_umbrella: true}]
end
Строка выше делает :kv доступной как зависимость внутри :kv_server и автоматически запускает приложение :kv перед запуском сервера.
Наконец, скопируйте kv приложение, которое мы создали до сих пор, в каталог apps в нашем новом проекте зонтика. Конечная структура каталогов должна соответствовать структуре, о которой мы упоминали ранее:
+ kv_umbrella
+ apps
+ kv
+ kv_server
Теперь нам нужно изменить apps/kv/mix.exs , чтобы оно содержало записи зонтика, которые мы видели в apps/kv_server/mix.exs. Откройте apps/kv/mix.exs и добавьте в функцию project/0:
build_path: "../../_build", config_path: "../../config/config.exs", deps_path: "../../deps", lockfile: "../../mix.lock",
Теперь вы можете запустить тесты для обоих проектов из корня проекта зонтика с помощью mix test. Отлично!
Не увлекайтесь
Проекты-зонтики — это удобство, которое помогает организовать и управлять несколькими приложениями. Хотя он обеспечивает определённую степень разделения между приложениями, эти приложения не полностью декомпозированы, так как они используют общую конфигурацию и зависимости.
Шаблон хранения нескольких приложений в одном репозитории известен как «моно-репозиторий». Проекты-зонтики максимизируют этот шаблон, предоставляя удобства для компиляции, тестирования и выполнения нескольких приложений одновременно.
Если вы обнаружите, что вам нужно использовать разные конфигурации для одной и той же зависимости в каждом приложении или использовать разные версии зависимостей, вероятно, ваш код вырос за пределы возможностей зонтиков.
Хорошая новость заключается в том, что разделить зонтик довольно просто, так как вам просто нужно переместить приложения за пределы каталога apps/ проекта зонтика и обновить файл mix.exs проекта, чтобы больше не устанавливать конфигурации build_path, config_path, deps_path, и lockfile. Вы можете зависеть от частных проектов за пределами зонтика несколькими способами:
- Переместите его в отдельную папку в одном репозитории и укажите на него с помощью зависимости по пути (шаблон моно-репозитория)
- Переместите репозиторий в отдельный репозиторий Git и зависьте от него
- Опубликуйте проект в частной организации Hex.pm
Подводя итоги
В этой главе мы узнали больше о зависимостях Mix и проектах-зонтиках. Хотя мы можем запустить kv без сервера, наш kv_server напрямую зависит от kv. Разделив их на отдельные приложения, мы получаем больший контроль над их разработкой и тестированием.
При использовании приложений-зонтиков важно иметь четкую границу между ними. Наше грядущее kv_server должно получать доступ только к общедоступным API, определённым в kv. Представьте себе приложения-зонтики как любые другие зависимости или даже сам Elixir: вы можете получить доступ только к тому, что является общедоступным и документированным. Обращение к приватной функциональности в ваших зависимостях — плохая практика, которая в конечном итоге приведёт к поломке вашего кода при обновлении до новой версии.
Приложения-зонтики также могут служить промежуточным этапом для последующего выделения приложения из вашего кода. Например, представьте веб-приложение, которое должно отправлять «push-уведомления» своим пользователям. Весь «система push-уведомлений» может быть разработан как отдельное приложение в зонтике со своей собственной древовидной структурой управления и API. Если в какой-то момент другому проекту потребуется система push-уведомлений, её можно переместить в частный репозиторий или пакет Hex.
Наконец, имейте в виду, что все приложения в проекте-зонтике используют одинаковые конфигурации и зависимости. Если два приложения в вашем зонтике должны настраивать одну и ту же зависимость совершенно разными способами или даже использовать разные версии, вероятно, вы вышли за рамки преимуществ, предоставляемых зонтиками. Помните, что вы можете разбить зонтик и всё равно воспользоваться преимуществами «моно-репозиториев».
После того, как наш проект-зонтик запущен, пришло время начать писать наш сервер.
© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.16.3/dependencies-and-umbrella-projects.html