Исходный код Зависимости и проекты-зонтики
В этой главе мы обсудим, как управлять зависимостями в 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. Отлично!
Не пейте колу
Проекты-зонтики — это удобство, которое поможет вам организовать и управлять несколькими приложениями. Хотя он обеспечивает определенную степень разделения между приложениями, эти приложения не полностью независимы, поскольку они используют одну и ту же конфигурацию и те же зависимости.
Паттерн размещения нескольких приложений в одном репозитории известен как «монорепозиторий». Проекты-зонтики максимизируют этот паттерн, предоставляя удобства для компиляции, тестирования и запуска нескольких приложений одновременно.
Если вы обнаружите, что хотите использовать разные конфигурации в каждом приложении для одной и той же зависимости или использовать разные версии зависимостей, то, скорее всего, ваш код вырос за пределы того, что могут обеспечить проекты-зонтики.
Хорошей новостью является то, что разбиение приложения на отдельные части довольно просто, так как вам нужно просто переместить приложения из каталога проекта umbrella в apps/ каталог и обновить файл mix.exs, чтобы больше не устанавливать build_path, config_path, deps_path, и lockfile конфигурацию. Вы можете зависеть от частных проектов вне приложения umbrella несколькими способами:
- Переместите его в отдельную папку в том же репозитории и укажите путь к нему с помощью зависимости по пути (паттерн mono-repo)
- Переместите репозиторий в отдельный репозиторий Git и зависите от него
- Опубликуйте проект в частную организацию Hex.pm
Подведение итогов
В этой главе мы узнали больше о зависимостях Mix и проектах umbrella. Хотя мы можем запустить kv без сервера, наш kv_server напрямую зависит от kv. Разбив их на отдельные приложения, мы получаем больший контроль над тем, как они разрабатываются и тестируются.
При использовании приложений umbrella важно четко разграничивать их. Наше предстоящее kv_server должно получать доступ только к общедоступным API, определенным в kv. Думайте о приложениях umbrella как об обычных зависимостях или даже о самом Elixir: вы можете получить доступ только к тому, что является общедоступным и документированным. Обращение к приватной функциональности в ваших зависимостях — плохая практика, которая в конечном итоге приведет к ошибке в вашем коде при обновлении до новой версии.
Приложения umbrella также могут использоваться как промежуточная ступень для последующей экстракции приложения из вашего кода. Например, представьте веб-приложение, которое должно отправлять «push-уведомления» своим пользователям. Всю «систему push-уведомлений» можно разработать как отдельное приложение в umbrella, со своим собственным деревом управления и API. Если в какой-то момент другому проекту понадобится система push-уведомлений, её можно переместить в частный репозиторий или пакет Hex.
Наконец, помните, что все приложения в проекте umbrella используют одни и те же конфигурации и зависимости. Если два приложения в вашем umbrella нуждаются в настройке одной и той же зависимости кардинально по-разному или даже используют разные версии, вы, вероятно, вышли за пределы преимуществ, предоставляемых umbrella. Помните, что вы можете разбить umbrella и все равно использовать преимущества «моно-репозиториев».
После того, как наш проект umbrella запущен, пришло время начать писать наш сервер.
© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.18.1/dependencies-and-umbrella-projects.html