Источник Зависимости и проекты-зонтики
В этой главе мы обсудим, как управлять зависимостями в 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 Package Manager, указав зависимость внутри функции 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.17.2/dependencies-and-umbrella-projects.html