Апи эликсир: Простейший JSON RESTful API на Эликсир / Хабр

0

Содержание

elixir – Маршрутизация Phoenix на основе полезной нагрузки

Я реализую backend API в Elixir / Phoenix. По соображениям безопасности было решено отображать как можно меньше в URL-адресе, чтобы выполняемое действие было встроено в полезные данные JSON запросов POST. Например, вместо REST-подобного запроса https://my.server.com/api/get_user/42 мы отправим POST на https://my.server.com/api/ со следующей полезной нагрузкой:

{
    "action" : "get_user",
    "params" : {
        "id" : 42
    }
}

Пока что мой роутер выглядит так:

  scope "/api", AlaaarmWeb do
     pipe_through :api

     post "/", ApiController, :process
     match :*, "/*path", ApiController, :error_404
   end

И у меня есть несколько функций process, которые соответствуют шаблону на карте params:

  def process(conn, params = %{"action" => "get_user"}) do
    # ...
get the user from the DB in resp, and sed back json(conn, resp) end

Он работает нормально, но код не очень ясен: один большой файл, используйте тот же контроллер, ApiController, хотя было бы гораздо проще иметь UserApiController для управления пользователями, {{X2} } для управления продуктами и т. д.

Итак, мне было интересно, есть ли способ сделать выбор модуля и функции для вызова также на основе содержимого полезной нагрузки, а не только на URL-адресе.

1

mszmurlo 2 Авг 2020 в 10:17

2 ответа

Лучший ответ

Если вы определили структуру как пару ключ-значение “действие” и “параметры”, поступающую через полезную нагрузку, и у вас есть десятки запросов, которые вы хотите фильтровать и делегировать различным контроллерам в зависимости от пути, тогда у вас должен быть такой плагин. определено в конечной точке. ex перед вызовом plug

YourApp.Router

  plug Plug.Session, @session_options
  plug UrlFromPayloadAction, []
  plug StackoverflowApiWeb.Router

Этот плагин изменяет информацию о пути, поэтому наш измененный URL-адрес будет анализироваться маршрутизатором, и мы можем определить контроллер, такой как post "/get_user/:id", ApiUserController, :process и другие, на основе определенного действия.

defmodule UrlFromPayloadAction do
        import Plug.Conn

        def init(default), do: default

        def call(%Plug.Conn{params: %{"action" => action,
                                         "params" => %{ "id" => id }
                                     }} = conn, _default) do
              conn = conn
                     |> assign(:original_path_info, conn.path_info)
                     |> assign(:original_request_path, conn.request_path)
               
              %Plug.Conn{conn | path_info: conn.path_info ++ [ "#{action}", "#{id}" ] , request_path: conn.request_path <> "/#{action}/#{id}" }
        end
        def call(conn, _default), do: conn
end

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

4

Gurwinder 2 Авг 2020 в 13:47

Нет, я не думаю, что Phoenix.Router может маршрутизировать в зависимости от полезной нагрузки. Это должно быть сделано в контроллере.

Но вы все равно можете создавать отдельные файлы / модули контроллера, как вы описываете, и просто use их все в «главном» ApiController.

0

Peaceful James 2 Авг 2020 в 11:12

elixir – Как создавать запросы GraphQL из Elixir API?

У меня есть Elixir API, который может использовать Graphiql для отправки запросов в мою базу данных, как вы можете видеть, все вызовы crud работают нормально.

field :users, list_of(:user) do
        resolve &Graphical.UserResolver.all/2
    end

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

users{
  id,
  name,
  email
}

В самом Эликсире, чтобы я мог вызвать его из внешнего интерфейса, чтобы вернуть все данные в формате JSON. Я совершенно не уверен, где и как писать запросы, чтобы они передавались в схему, а об остальном позаботились Absinthe и Ecto.

0

Errol Hassall 5 Фев 2018 в 08:41

2 ответа

Лучший ответ

Может что-то сделать Запрос:

users(scope: "some"){}

И в резольвере

defmodule Graphical.UserResolver do

  def all(_root,  %{:scope => "some"}, _info) do
    users = Graphical.Repo.all(from user in Graphical.User,
       select: { user.name, user.email, user.id }
     ) 
    {:ok, users}
  end

  def all(_root, _args, _info) do
    users = Graphical.Repo.all(Graphical.User)
    {:ok, users}
  end

end

1

Keval Gohil 6 Фев 2018 в 07:30

Когда вы запрашиваете Absinthe из FE, вы получаете взамен объект JSON. Если вы хотите, чтобы поле возвращало объект JSON, вам нужно будет создать специальный скаляр и использовать его в качестве типа поля. например

defmodule ApiWeb.Schema.Types.JSON do
  @moduledoc """
  The Json scalar type allows arbitrary JSON values to be passed in and out.
  Requires `{ :jason, "~> 1.1" }` package: https://github.com/michalmuskala/jason
  """
  use Absinthe.Schema.Notation

  scalar :json, name: "Json" do
    description("""
    The `Json` scalar type represents arbitrary json string data, represented as UTF-8
    character sequences. The Json type is most often used to represent a free-form
    human-readable json string.
    """)

    serialize(&encode/1)
    parse(&decode/1)
  end

  @spec decode(Absinthe.Blueprint.Input.String.t()) :: {:ok, term()} | :error
  @spec decode(Absinthe.Blueprint.Input.Null.t()) :: {:ok, nil}
  defp decode(%Absinthe.Blueprint.Input.String{value: value}) do
    case Jason.decode(value) do
      {:ok, result} -> {:ok, result}
      _ -> :error
    end
  end

  defp decode(%Absinthe.Blueprint.Input.Null{}) do
    {:ok, nil}
  end

  defp decode(_) do
    :error
  end

  defp encode(value), do: value
end

А потом

    @desc "Create an auth token"
    field :create_auth_token, :token do
      arg(:app_id, non_null(:string))
      arg(:sub, non_null(:string))
      arg(:claims, :json)
      resolve(&Resolvers.Token.create_auth_token/3)
    end
    ```

0

Ian 7 Июл 2019 в 23:06

Параллелизм в OTP · Elixir School

Мы уже рассматривали абстракции языка Elixir для параллельного выполнения, но иногда нужна более широкая функциональность, и тогда мы обращаемся к поведениям OTP.

В этом уроке мы сосредоточимся на GenServer.

Сервер OTP &mdash; это модуль с GenServer, который имплементирует набор функций обратного вызова (callback). На простейшем уровне GenServer &mdash; это один процесс, обрабатывающий в цикле по одному запросу за итерацию, сохраняя обновленное состояние.

Для демонстрации API GenServer мы реализуем базовую очередь для хранения и извлечения значений.

Для создания GenServer нужно запустить его и позаботиться об инициализации. В большинстве случаев мы хотим связать процессы, потому используем GenServer.start_link/3. Мы передаем туда запускаемый GenServer модуль, начальные аргументы и набор настроек для самого GenServer. Эти настройки будут переданы в GenServer.init/1, который и задает начальное состояние с помощью возвращаемого значения. В этом примере аргументы и будут начальным состоянием:

defmodule SimpleQueue do
  use GenServer

  @doc """
  Запуск и линковка нашей очереди. Это вспомогательный метод.
  """
  def start_link(state \\ []) do
    GenServer.start_link(__MODULE__, state, name: __MODULE__)
  end

  @doc """
  Функция обратного вызова для GenServer.init/1
  """
  def init(state), do: {:ok, state}
end

Синхронные функции

Часто необходимо взаимодействовать с GenServer в синхронном формате, вызывая функцию и ожидая ее ответа. Для обработки синхронных запросов важно имплементировать метод GenServer.handle_call/3, который передает запрос, PID вызывающего процесса и текущее состояние. Ожидается, что будет возвращен кортеж {:reply, response, state}.

С помощью сопоставления с образцом можно определять функции обратного вызова для разных запросов и состояний. Полный список допустимых вариантов возврата есть в документации GenServer.handle_call/3.

Для демонстрации синхронных запросов давайте добавим возможность отображать текущее состояние очереди и удаление значений:

defmodule SimpleQueue do
  use GenServer

  ### GenServer API

  @doc """
  Функция обратного вызова для GenServer.init/1
  """
  def init(state), do: {:ok, state}

  @doc """
  Функции обратного вызова для GenServer.handle_call/3
  """
  def handle_call(:dequeue, _from, [value | state]) do
    {:reply, value, state}
  end

  def handle_call(:dequeue, _from, []), do: {:reply, nil, []}

  def handle_call(:queue, _from, state), do: {:reply, state, state}

  ### Клиентский API

  def start_link(state \\ []) do
    GenServer.start_link(__MODULE__, state, name: __MODULE__)
  end

  def queue, do: GenServer.call(__MODULE__, :queue)
  def dequeue, do: GenServer.call(__MODULE__, :dequeue)
end

Давайте запустим наш SimpleQueue и проверим как работает новая функциональность:

iex> SimpleQueue.start_link([1, 2, 3])
{:ok, #PID<0.90.0>}
iex> SimpleQueue.dequeue
1
iex> SimpleQueue.dequeue
2
iex> SimpleQueue.queue
[3]

Асинхронные функции

Асинхронные запросы обрабатываются функциями обратного вызова handle_cast/2. Это работает приблизительно так же как и handle_call/3, но не получает данных об отправителе и такой метод не обязан ничего отвечать.

Мы реализуем функциональность добавления элемента в очередь асинхронно &mdash; обновляя очередь, но не блокируя вызывающий код:

defmodule SimpleQueue do
  use GenServer

  ### GenServer API

  @doc """
  Функция обратного вызова для GenServer.init/1
  """
  def init(state), do: {:ok, state}

  @doc """
  Функции обратного вызова для GenServer.handle_call/3
  """
  def handle_call(:dequeue, _from, [value | state]) do
    {:reply, value, state}
  end

  def handle_call(:dequeue, _from, []), do: {:reply, nil, []}

  def handle_call(:queue, _from, state), do: {:reply, state, state}

  @doc """
  Функция обратного вызова для GenServer.handle_cast/2
  """
  def handle_cast({:enqueue, value}, state) do
    {:noreply, state ++ [value]}
  end

  ### Клиентский API

  def start_link(state \\ []) do
    GenServer.start_link(__MODULE__, state, name: __MODULE__)
  end

  def queue, do: GenServer.call(__MODULE__, :queue)
  def enqueue(value), do: GenServer.cast(__MODULE__, {:enqueue, value})
  def dequeue, do: GenServer.call(__MODULE__, :dequeue)
end

И попробуем эту новую функциональность:

iex> SimpleQueue.start_link([1, 2, 3])
{:ok, #PID<0.100.0>}
iex> SimpleQueue.queue
[1, 2, 3]
iex> SimpleQueue.enqueue(20)
:ok
iex> SimpleQueue.queue
[1, 2, 3, 20]

Для получения дополнительной информации можно обратиться к официальной документации GenServer.

Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!

Национальный театр Карелии | Эликсир любви

                                 

 

 

 

Дата премьеры:  25 декабря 2015 года
Дата капитального возобновления: 13 февраля 2019 года
Язык: Русский       
Сцена:  Большая
Продолжительность: 1 час 15 минАнтракт:  нет

 


 

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

Это история о докторе Мишеле, который изобрел любовный эликсир, но, стараниями слуги Огюста, Мишель чуть не попадает на костер инквизиции. Остроумная любовная фабула и живость положений позволила не придерживаться каких-то временных ориентиров, а создать спектакль, соединяющий средневековье и современность, маску комедии dell’arte и современные кеды. Зрителей ждет радуга мыльных пузырей, ящик с сюрпризом, французский шансон, чудеса и превращения.

 


 

 

СОЗДАТЕЛИ

 

ДЕЙСТВУЮЩИЕ ЛИЦА И ИСПОЛНИТЕЛИ

Режиссёр-постановщик – Андрей ДЕЖОНОВ       

Мишель –

Петр КАСАТЬЕВ

 

Режиссёр по пластике – заслуженный артист Карелии

                                    Александр ОВЧИННИКОВ 

 

Огюст, его слуга –

Глеб ГЕРМАНОВ

Мари –

Анни КОНДРАКОВА

Режиссёр по танцу – заслуженный артист Карелии

                                               Олег ЩУКАРЕВ

 

Чертёнок –

Анастасия АЙТМАН

Великий инквизитор –

Ксения ШИРЯКИНА

Режиссёр по сценической речи – Инна ПРОВОТОРОВА         

Сентиментальный палач –

Дмитрий ИВАНОВ

Помощник режиссёра – Полина МИШИНА 

ЖЕНЩИНЫ ГОРОДА:

 В создании спектакля принимали участие художники:

Женственная – 

Людмила ИСАКОВА

Мужественная – 

Настасья ШУМ

Галина Филюшкина

– по декорациям: Игорь ТИХОНОВ и Егор КУКУШКИН
– по свету: Денис КОРОЛЁВ и Алексей ВОРОНИН
– по звуку: Олег ГОДАРЕВ
– по гриму: Виктория КУДРЯВЦЕВА
– по костюмам: Ирина КАВУНЕНКО и Ассоль ЗЕЛАНД

Притягательная – 

Лада КАРПОВА

Странная – 

Ксения ШИРЯКИНА

 МУЖЧИНЫ ГОРОДА: они же
В спектакле используются песни на музыку
и стихи Ксении Ширякиной (в исполнении автора)
 

 

 

GenServer |> Сообщество Elixir и Phoenix Framework

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

CREATE shopping
OK

PUT shopping milk 1
OK

GET shopping milk
1
OK

Поскольку агенты – это процессы, каждая корзина имеет свой идентификатор процесса, но не имеет названия. Мы узнали о регистрации имени в главе «Процессы», и вы могли бы решить эту проблему с помощью такой регистрации. Например, можно создать корзину так:

iex> Agent.start_link(fn -> %{} end, name: :shopping)
{:ok, #PID<0.43.0>}

iex> KV.Bucket.put(:shopping, "milk", 1)
:ok

iex> KV.Bucket.get(:shopping, "milk")
1

Тем не менее, это ужасная идея! Имена процессов в Эликсире должны быть атомами. Это означает, что нужно преобразовать название корзины (часто получаемое из внешнего источника) в атомы. Ни в коем случае нельзя преобразовывать пользовательский ввод в атомы. Сборщик мусора не собирает атомы. Генерация атомов из пользовательского ввода может привести к исчерпанию системной памяти!

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

Реестр должен гарантировать, что словарь всегда находится в актуальном состоянии. Например, если один из процессов падает из-за ошибки, реестр должен заметить это изменение и избежать обслуживания устаревших сущностей. Принято говорить, что реестр в Эликсире должен мониторить каждую корзину.

Мы будем использовать модуль GenServer для создания реестра процессов, который может мониторить процессы корзин. Модуль GenServer обеспечивает надежную промышленную функциональность для создания серверов на Эликсире и OTP.

Наш первый

GenServer

Модуль GenServer состоит из двух частей: клиентского API и серверных колбэков. Вы можете либо объединить обе части в один модуль, либо разделить их на клиентский и серверный модули. Клиент и сервер работают в отдельных процессах, при этом клиент передает сообщения обратно на сервер, и принимает сообщения при вызове его функций. Здесь мы объединим серверные колбэки и клиентский API в одном модуле.

Создайте новый файл lib/kv/registry.ex со следующим содержимым:

defmodule KV.Registry do
  use GenServer

  ## Client API

  @doc """
  Запускает реестр.
  """
  def start_link(opts) do
    GenServer.start_link(__MODULE__, :ok, opts)
  end

  @doc """
  Ищет идентификатор процесса корзины для `name`, сохраненного в `server`.

  Возвращает `{:ok, pid}`, если корзина существует, иначе `:error`.
  """
  def lookup(server, name) do
    GenServer.call(server, {:lookup, name})
  end

  @doc """
  Обеспечивает наличие корзины, связанной с указанным `name` в `server`.
  """
  def create(server, name) do
    GenServer.cast(server, {:create, name})
  end

  ## Серверные колбэки

  def init(:ok) do
    {:ok, %{}}
  end

  def handle_call({:lookup, name}, _from, names) do
    {:reply, Map.fetch(names, name), names}
  end

  def handle_cast({:create, name}, names) do
    if Map.has_key?(names, name) do
      {:noreply, names}
    else
      {:ok, bucket} = KV.Bucket.start_link([])
      {:noreply, Map.put(names, name, bucket)}
    end
  end
end

Первая функция start_link/3 запускает GenServer и передает ему три аргумента:

  1. Модуль, в котором реализованы серверные колбэки, в нашем случае __MODULE__ – значение текущего модуля;

  2. Аргументы инициализации, в нашем случае – атом :ok;

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

Есть два типа запросов, которые можно отправлять в GenServer: call и cast. Запрос типа call – синхронный, и сервер должен ответить на него. Запрос типа cast – асинхронный, и сервер на них не отвечает.

Следующие две функции: lookup/2 и create/2, ответственны за отправку этих запросов к серверу. В нашем случае, мы воспользовались {:lookup, name} и {:create, name} соответственно. Запросы зачастую описываются в виде таких кортежей, чтобы иметь возможность разместить несколько «аргументов» в одном слоте. Как правило, заправшиваемое действие указывается первым элементом кортежа, а оставшиеся элементы являются аргументами. Обратите внимание, что запросы должны соответствовать первым аргументам функций handle_call/3 или handle_cast/2.

С клиентским API закончили. На стороне сервера мы можем реализовать различные колбэки, чтобы гарантировать инициализацию сервера, его отключение и обработку запросов. Эти колбэки являются необязательными, и сейчас мы реализовали только нужные нам.

Первый – колбэк init/1. Он принимает на вход второй аргумент из функции GenServer.start_link/3 и возвращает кортеж {:ok, state}, где состояние представляет собой новый словарь. Уже можно заметить, что API модуля GenServer делает разделение на клиентскую и серверную части более очевидным. Функция start_link/3 выполняется на клиенте, а функция init/1 – это соответствующий колбэк, который выполняется на сервере.

Для запросов типа call/2 реализуется колбэк handle_call/3, который принимает request, процесс, отправивший запрос (_from) и текущее состояние сервера (names). Колбэк handle_call/3 возвращает кортеж в формате {:reply, reply, new_state}. Первый элемент кортежа, :reply, показывает, что сервер должен отправлять ответ обратно клиенту. Второй элемент, reply, будет отправлен клиенту, а третий элемент, new_state, представляет собой новое состояние сервера.

Для запросов типа cast/2 реализуется колбэк handle_cast/2, который принимает request и текущее состояние сервера (names). Колбэк handle_cast/2 возвращает кортеж в формате {:noreply, new_state}. Обратите внимание, что в реальном приложении мы бы, наверное, реализовали колбэк для запроса :create с синхронным вызовом вместо асинхронного. Сейчас мы просто иллюстрируем, как реализовать асинхронный вызов.

Есть и другие форматы кортежей для функций handle_call/3 и handle_cast/2, которые могут возвращать колбэки. Так же есть и другие колбэки, такие как terminate/2 и code_change/3, которые можно было бы реализовать. Вы можете изучить полную документацию модуля GenServer, чтобы узнать о них больше.

Теперь давайте напишем несколько тестов, чтобы гарантировать правильную работу GenServer.

Тестирование

GenServer

Тестирование GenServer не сильно отличается от тестирования агента Agent. Мы создадим сервер в колбэке setup и будем использовать его во время наших тестов. Создайте файл test/kv/registry_test.exs со следующим содержимым:

defmodule KV.RegistryTest do
  use ExUnit.Case, async: true

  setup do
    {:ok, registry} = start_supervised KV.Registry
    %{registry: registry}
  end

  test "spawns buckets", %{registry: registry} do
    assert KV.Registry.lookup(registry, "shopping") == :error

    KV.Registry.create(registry, "shopping")
    assert {:ok, bucket} = KV.Registry.lookup(registry, "shopping")

    KV.Bucket.put(bucket, "milk", 1)
    assert KV.Bucket.get(bucket, "milk") == 1
  end
end

Наш тест должен сразу же выполниться без ошибок!

Напомним, что благодаря функции start_supervised, модуль ExUnit следит за завершением процесса реестра после выполнения каждого теста. Если необходимость остановить GenServer – это часть логики приложения, можно использовать функцию GenServer.stop/1:

## Клиентский API

@doc """
Остановка реестра.
"""
def stop(server) do
  GenServer.stop(server)
end

Необходимость мониторинга

Наш реестр почти готов. Единственный оставшийся вопрос заключается в том, что реестр может устареть, если процесс корзины остановится или упадет. Давайте добавим тест в KV.RegistryTest, который выявляет эту ошибку:

test "removes buckets on exit", %{registry: registry} do
  KV.Registry.create(registry, "shopping")
  {:ok, bucket} = KV.Registry.lookup(registry, "shopping")
  Agent.stop(bucket)
  assert KV.Registry.lookup(registry, "shopping") == :error
end

Этот тест упадет, так как имя корзины остается в реестре даже после остановки процесса корзины.

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

Давайте поиграем с мониторингом в консоли, выполнив команду iex ‐S mix:

iex> {:ok, pid} = KV.Bucket.start_link
{:ok, #PID<0.66.0>}

iex> Process.monitor(pid)
#Reference<0.0.0.551>

iex> Agent.stop(pid)
:ok

iex> flush()
{:DOWN, #Reference<0.0.0.551>, :process, #PID<0.66.0>, :normal}

Заметьте, что функция Process.monitor(pid) возвращает уникальную ссылку, которая позволяет сопоставлять ей входящие сообщения процесса. После остановки агента, можно посмотреть все входящией сообщения при помощи функции flush/0. Обратите внимание, что пришло сообщение :DOWN с той же самой ссылкой на процесс корзины, что вернул монитор. Отметим, что процесс корзины завершен с причиной :normal.

Давайте переопределим серверные колбэки для исправления этого бага, и перезапустим тест. Сначала, разделим состояние GenServer на два словаря: один будет содержать соответствие name ‐> pid, а другой – ref ‐> name. Затем добавим мониторинг процесса корзины в функцию handle_cast/2, а также напишем колбэк handle_info/2 для обработки сообщений мониторинга. Полная реализация всех серверных колбэков показана ниже:

## Серверные колбэки

def init(:ok) do
  names = %{}
  refs  = %{}
  {:ok, {names, refs}}
end

def handle_call({:lookup, name}, _from, {names, _} = state) do
  {:reply, Map.fetch(names, name), state}
end

def handle_cast({:create, name}, {names, refs}) do
  if Map.has_key?(names, name) do
    {:noreply, {names, refs}}
  else
    {:ok, pid} = KV.Bucket.start_link([])
    ref = Process.monitor(pid)
    refs = Map.put(refs, ref, name)
    names = Map.put(names, name, pid)
    {:noreply, {names, refs}}
  end
end

def handle_info({:DOWN, ref, :process, _pid, _reason}, {names, refs}) do
  {name, refs} = Map.pop(refs, ref)
  names = Map.delete(names, name)
  {:noreply, {names, refs}}
end

def handle_info(_msg, state) do
  {:noreply, state}
end

Обратите внимание, что мы смогли значительно изменить реализацию сервера без изменения какого-либо клиентского API. Это одно из преимуществ явного разделения сервера и клиента.

Наконец, в отличие от других колбэков, мы определили условие «поймать все» для функции handle_info/2, которая отбрасывает любые неизвестные сообщения. Чтобы понять зачем это нужно, перейдем к следующему разделу.

Какой колбэк выбрать?

До сих пор мы пользовались тремя колбэками: handle_call/3, handle_cast/2 и handle_info/2. Вот что мы должны учитывать при принятии решения, когда следует использовать каждый:

  1. Колбэк handle_call/3 должен быть использован для синхронных запросов. Он является выбором по умолчанию, поскольку ожидание ответа сервера является полезным механизмом противодавления.

  2. Колбэк handle_cast/2 должен быть использован для асинхронных запросов, когда не нужно заботиться об ответе. Запрос типа cast не дает гарантии получения запроса сервером. По этой причине его следует использовать с осторожностью. Например, функция create/2, которую мы определили в этой главе, должна использовать функцию call/2. Мы использовали функцию cast/2 для обучающих целей.

  3. Колбэк handle_info/2 должен использоваться для всех других сообщений, которые не передаются через функции GenServer.call/2 или GenServer.cast/2, включая отправку обычных сообщения функцией send/2. Мониторинг сообщений :DOWN – как раз такой пример.

Поскольку любое сообщение, в том числе отправленное через функцию send/2, попадают в колбэк handle_info/2, есть шанс, что на сервер будут приходить непредвиденные сообщения. Поэтому, если мы не определим условие «поймать все», нужного перехватчика не будет найдено, и эти сообщения могут уронить реестр. Нам не нужно беспокоиться о таких случаях для функций handle_call/3 и handle_cast/2. Запросы типов call и cast выполняются только через API GenServer, так что неизвестные сообщения, скорее всего, ошибка разработчика.

Чтобы помочь разработчикам запомнить различия между запросами типов call, case и info, поддерживаемые возвращаемые значения и т. д., Бенджамин Тан Вэй Хао подготовил отличную шпаргалку для модуля GenServer.

Мониторы или ссылки?

Мы узнали о ссылках в главе «Процессы». Теперь, с завершением работы над реестром, вам может быть интересно: когда следует использовать мониторы, а когда ссылки?

Ссылки двунаправлены. Если вы связываете два процесса, и один из них выйдет из строя, другая сторона тоже выйдет из строя (если только не перехватываются сигналы выхода). Монитор является однонаправленным: процесс мониторинга будет только получать уведомления о контролируемом. Другими словами: используйте ссылки, когда вы хотите получить связанные падения, и мониторы, когда нужно лишь получать информацию о падениях, выходах и т. п.

Возвращаясь к нашей реализации колбэка handle_cast/2, вы можете видеть, что реестр является как связующим, так и мониторящим процессы корзин:

{:ok, pid} = KV.Bucket.start_link
ref = Process.monitor(pid)

Это плохая идея, т. к. реестр не должен падать при падении корзины! Обычно, мы избегаем прямого создания новых процессов, вместо этого делегируя эту ответственность супервизорам. Как мы увидим в следующей главе, супервизоры полагаются на ссылки. Это объясняет, почему API на основе ссылок (например, функции spawn_link, start_link т. д.) настолько распространены в Эликсире и OTP.

Влияние зубного эликсира “Виноградный” на уровень маркеров воспаления и дисбиоза в десне крыс с экспериментальным гингивитом Текст научной статьи по специальности «Фундаментальная медицина»

16. A comparative study of normal inspection, autofluorescence and 5-ALA-induced PPIX fluorescence for oral cancer diagnosis /C.S. Betz, H. Stepp, P. Janda et al. // Int. J. Cancer. -2002. – Vol. 97, № 2. – P. 245-252.

17. Левицкий А. П. Сравнительная характеристика трех методов определения фосфатаз слюны человека / А. П. Левицкий, А. И. Марченко, Т. Л. Рыбак // Лабораторное дело. – 1973. – № 10. – С. 624-625.

18. Visser L. The use of p-nitrophenyl-N-testbutyloxycarbonyl-L-alaninate as substrate for elastase / L. Visser, E. R. Blouf // Biochem. Biophys. Acta. – 1972. – Vol. 268, N 1. – Р. 275-280.

19. Determination of bioactive rat parathyroid hormone (PTH) concentrations in vivo and in vitro by a 2-site homologous immuno-radiometric assay / V.L. Schultz, S.C. Gamer, J.R. Lavigne, S.U. Toverud // Bone and Mineral.- 1994. – Vol. 27, № 2. – P. 121-132.

20. Лабораторные методы исследования в клинике: справочник / В. В. Меньшиков, Л. Н. Делеторская, Р. П. Зо-лотницкая [и др.] ; под ред. В. В. Меньшикова. – М. : Медицина, 1987. – 368 с.

Надшшла 22.06.11

Ключов1 слова: ясна, гiнгiвiт, duc6io3, листя винограду, зу-бний елжсир.

V. V. Lepskij, V. V. Lepskij, L. N. Khromagina, O. E. Knava

SE “The Institute of Dentistry of the NAMS of Ukraine”

THE INFLUENCE OF DENTIFRICE WATER “VINOGRADNYJ” UPON THE LEVEL OF MARKERS OF INFLAMMATION AND DISBIOSIS IN GUMS OF RATS WITH EXPERIMENTAL GINGIVITIS

At the simulation of gingivitis by application of apitoxin suspension the development of inflammatory (the growth of activity of elastase and concentration of MDA) and disbiotic (the growth of activity of urease and the degree of disbiosis) phenomena in gum was found. The irrigation of oral cavity with diluted dentifrice water “Vinogradnyj ” (“Grape “) removed almost to the norm all pathological phenomena in gum.

Key words: gum, gingivitis, disbiosis, grape leaves, dentifrice water.

УДК 616.36-002-07:616.316-078.33

В. В. Лепский, В. В. Лепский, к. мед.н.

Л. Н. Хромагина, к. биол. н. О. Э. Кнава.

ГУ «Институт стоматологии НАМН Украины»

ВЛИЯНИЕ ЗУБНОГО ЭЛИКСИРА «ВИНОГРАДНЫЙ» НА УРОВЕНЬ МАРКЕРОВ ВОСПАЛЕНИЯ И ДИСБИОЗА В ДЕСНЕ КРЫС С ЭКСПЕРИМЕНТАЛЬНЫМ ГИНГИВИТОМ

При моделировании гингивита путем аппликаций суспензии пчелиного яда установлено развитие в десне воспалительных (увеличение активности эластазы и концентрации МДА) и дисбиотических (увеличение активности уреазы и степени дисбиоза) явлений. Орошение полости рта разведенным зубным эликсиром «Виноградный» устраняло почти до нормы все патологические явления в десне. Ключевые слова: десна, гингивит, дисбиоз, листья винограда, зубной эликсир.

В. В. Лепський, В. В. Лепський, Л. М. Хромагта, О. Е. Кнава

ВПЛИВ ЗУБНОГО ЕЛ1КСИРУ «ВИНОГРАД-НИЙ» НА Р1ВЕНЬ МАРКЕР1В ЗАПАЛЕННЯ

I ДИСБ1ОЗУ В ЯСНАХ ЩУР1В З ЕКСПЕРИМЕНТАЛЬНИМ Г1НГ1В1ТОМ

При моделювант гiнгiвiту шляхом апткацш суспензи бджолиног отрути встановлено розвиток в яснах запальних (тдвищення активностi еластази i концентраци МДА) та дiзбiотичних (збыьшення активностi уреази i ступеню дис-бiозу) явищ. Зрошення порожнини рота розведеним зубним елжсиром «Виноградний» усувало майже до норми уЫ па-тологiчнi явища в яснах.

Листья винограда являются богатейшим источником биологически активных веществ, среди которых особое место занимают соединения полифеноль-ного ряда [1]. В составе полифенолов винограда обнаружены биофлавоноиды, обладающие Р-витаминной активностью [2], антоцианы, которым свойственно противоязвенное действие [3], хлорогеновая кислота, оказывающая антимикробное и гепатопротекторное действие [4], ресвератрол, который угнетает развитие злокачественных новообразований [5].

Все эти полифенольные вещества хорошо растворяются в водно-спиртовых растворах и сравнительно легко извлекаются из листьев винограда экстракцией спиртом.

Используя водно-спиртовый экстракт из листьев винограда, был получен зубной эликсир «Виноградный», в состав которого входило 20 % экстракта, це-тавлон, ментол, отдушка.

Материалы и методы исследования. Экспериментальный гингивит воспроизводили у крыс с помощью пчелиного яда [7]. Суспензию пчелиного яда (0,5 мг/мл) в количестве 0,5 мл наносили на десну крыс один раз в день в течение трех дней. Через 1 час после нанесения яда орошали полость рта либо водой (2-ая группа), либо разведенным в 5 раз эликсиром (2,5 мл). Орошение производили один раз в день в течение трех дней. Умерщвление животных осуществляли под тиопенталовым наркозом (20 мг/кг) путем тотального кровопускания из сердца. Иссекали мягкие ткани десны и хранили до исследования при минус 30 °С.

В гомогенатах десны (20 мг/мл физиологического раствора) определяли уровень маркеров воспаления [8]: активность эластазы и концентрацию малонового диальдегида (МДА). Определяли также активность антиоксидантного фермента каталазы и уровень антиоксидантно-прооксидантного индекса АПИ [8].

© Лепский В. В., Лепский В. В., Хромагина Л. Н., Кнава О. Э, 2011.

“Вiсник стоматологИ”, № 3, 2011

23

Микробную обсемененность десны оценивали по активности уреазы [9]; уровень неспецифической антимикробной защиты – по активности лизоцима [9]. По соотношению относительных активностей уреазы и лизоцима рассчитывали степень дисбиоза [9].

Результаты исследований и их обсуждение. На рис. 1 представлены результаты определения в десне маркеров воспаления – активности эластазы и концентрации МДА. Как видно из этих данных, при моделировании гингивита в десне достоверно увеличи-

вается уровень обоих маркеров воспаления. Орошение полости рта зубным эликсиром снижает эти показатели практически до нормы.

Напротив, активность в десне антиоксидантного фермента каталазы (рис. 2) достоверно снижается при гингивите и проявляет тенденцию к повышению после применения эликсира.

Рис. 1. Влияние эликсира «Виноградный» на уровень маркеров воспаления в десне крыс при гингивите: 1 – норма; 2 -гивит; 3 – гингивит+эликсир (*- р<0,05 в сравнении с нормой; **- р<0,05 в сравнении с гингивитом).

Рис. 2. Влияние эликсира «Виноградный» на активность каталазы и индекс АПИ в десне крыс с гингивитом (обозначение -см. рис. 1)

Более показательны изменения индекса АПИ: он достоверно снижается при гингивите и достоверно увеличивается (вплоть до нормы) при орошении полости рта эликсиром (рис. 2).

На рис. 3 представлены результаты определения в десне активности уреазы, лизоцима и степени дис-биоза. Как видно из этих данных, при гингивите достоверно увеличивается активность уреазы, что свидетельствует о росте микробной обсемененности этой ткани при моделировании гингивита. Орошение полости рта эликсиром снижает активность уреазы до нормы (правда, по сравнению с группой нелеченных

крыс р>0,05 из-за больших индивидуальных разбросов).

Активность лизоцима проявляет тенденцию к снижению при гингивите и тенденцию к увеличению после применения эликсира. Однако, значение р во всех случаях более 0,05 из-за больших индивидуальных разбросов.

Рассчитанная по методу Левицкого [9] степень дисбиоза (рис. 3) показывает более чем троекратное увеличение этого показателя при гингивите и достоверное снижение (почти до нормы) после применения зубного эликсира.

Рис. 3. Влияние эликсира «Виноградный» на активность уреазы, лизоцима и степень дисбиоза в десне крыс с гингивитом (обозначение – см. рис. 1)

Таким образом, полученные нами данные свидетельствуют о противовоспалительном и антидисбио-тическом действии биологически активных веществ из листьев винограда (прежде всего, за счет большого набора различных полифенолов [10]). Ряд из них (хлорогеновая кислота) обладает антимикробной активностью, другие (антоцианы, флавононы, катехи-ны), подавляя свободно-радикальные процессы и снижая активность провоспалительных ферментов (фосфолипазы А2, протеаз, липоксигеназ) [11], осуществляют противовоспалительное действие. Эти результаты дают основание для использования зубного эликсира «Виноградный» в стоматологии, в частности, для профилактики и лечения гингивитов.

Список литературы

1. Andersen O. M. Flavanoids: Chemistry, Biochemistry and Applications / O. M. Andersen, K. R. Markham. – Taylor and Francis CRC Press, 2005. – 1256 с.

2. Мука из виноградных листьев – источник витамина Р в комбикормах / А. П. Левицкий, В. Т. Гулавский, И. В. Ходаков [и др.] // Зерновi продукти i комбжорми. – 2011. -№ 1 (41). – С. 30-33.

3. Clifford M. N. Anthocyanins – nature, occurrence and dietary burden / M. N. Clifford // J. Sci. Food and Agr. [МФИШ]. – 2000. – 80, № 7. – С. 1063-1072.

4. Левицкий А. П. Хлорогеновая кислота: биохимия и физиология / А. П. аскорбат-шдукованого перекисного окислення лiпiдiв / О. В. Файзуллш, Л. М. Ворошна, А. Л. Загайко // Медична хiмiя. – 2005. – Т. 7, № 4. – С. 77-79.

7. Пат. На корисну модель № 31011 UA, МПК (2006) А 61 Р 31/00, А 61 С 7/00, А 61 К 35/56. СпоЫб моделюван-ня пнггвпу / Левицький А. П., Селiванська I. О., Макаренко О. А. [та ш.]. – Опубл. 2008. – Бюл. № 6.

8. Биохимические маркеры воспаления тканей ротовой полости: метод. Рекомендации / А. П. Левицкий, О. В. Деньга, О. А. Макаренко и др. – Одесса, 2010. – 16 с.

9. Пат. На корисну модель № 16048 UA. Cnoci6 оцш-ки дисбактерюзу порожнини рота / Левицький А. П., Макаренко О. А., Селiванська I. О. [та îh.]. – Опубл. 17.07.2006. -Бюл. № 7.

10. Тутельян В. А. Биологически активные вещества растительного происхождения. Катехины: пищевые источники, биодоступность, влияние на ферменты метаболизма ксенобиотиков / В. А. Тутельян, Н. В. Лашнева // Вопросы питания. -2009. -Т. 78, № 4. – С. 4-20.

11. Middleton E., Jr. The effects of plant flavonoids on mammalian cells: implications for inflammation, heart disease, and caroer / E. Jr. Middleton, C. Kandaswami, T. C. Theoha-rides // Pharmacol. Rev. -2000. -V. 52, № 4. – P. 673-751.

Поступила 04.07.11

УДК 616.314-089.281+678.048:615.451

Н. В. Рожкова, В. А. Лабунец, д. мед. н., В. В. Лепский, к. мед. н., В. В. Лепский

ГУ “Институт стоматологии НАМН Украины”

СРАВНИТЕЛЬНАЯ АНТИДИСБИОТИЧЕСКАЯ ЭФФЕКТИВНОСТЬ ЗУБНЫХ ЭЛИКСИРОВ ПРИ ЭКСПЕРИМЕНТАЛЬНОМ ДИСБИОЗЕ СЛИЗИСТОЙ ПОЛОСТИ РТА

В эксперименте на крысах при моделировании дисбиоза слизистой полости рта с помощью линкомицина установлено антидисбиотическое действие ряда зубных эликсиров (“Экстравин-Дента”, “Биодент-4”, “Лизомукоид”) и виноградного концентрата “Экстравин”. Наиболее эффективными оказались “Экстравин-Дента” и “Лизомукоид”. Ключевые слова: дисбиоз слизистой полости рта, зубные эликсиры, антидисбиотическое действие.

© РожковаН. В., Лабунец В. А., Лепский В. В., Лепский В. В., 2011.

Смешарики. Эликсир обновить историю версий для Android

Увлекательная игра по серии “Эликсир молодости” сериала “Смешарики”

Предыдущие версии

  • V1.1.4 110.0 MB APK

    Смешарики. Эликсир

    2020-09-13

    Смешарики. Эликсир 1.1.4 (28)

    Обновлено: 2020-09-13

    Uploaded by: Waitfor You

    Требуется Android: Android 4.4+ (Kitkat, API 19)

    Подпись: 621f138320795cbacc1819bf9d7857b0b476f874

    DPI: 120-640dpi

    Arch: arm64-v8a, armeabi-v7a

    Файл SHA1-хэша: 2a425382ddf714b81b6858e2e9471993f9e683c7

    Размер файла: 110.0 MB

    Что нового:

    Скачать

  • V1.1.3 83.4 MB APK

    Смешарики. Эликсир

    2019-07-31

    Смешарики. Эликсир 1.1.3 (26)

    Обновлено: 2019-07-31

    Uploaded by: Varayut Phetkaew

    Требуется Android: Android 4.1+ (Jelly Bean, API 16)

    Подпись: 621f138320795cbacc1819bf9d7857b0b476f874

    DPI: 120-640dpi

    Arch: armeabi-v7a, x86

    Файл SHA1-хэша: 218ecb831eff0707eb5bd2e03c54e8a4f02a5f16

    Размер файла: 83.4 MB

    Что нового:

    Скачать

  • V1.1.2 83.4 MB APK

    Смешарики. Эликсир

    2018-10-30

    Смешарики. Эликсир 1.1.2 (24)

    Обновлено: 2018-10-30

    Uploaded by: ဆင္ျဖဴရြာက ခ်စ္ေတးတစ္ပုဒ္

    Требуется Android: Android 4.1+ (Jelly Bean, API 16)

    Подпись: 621f138320795cbacc1819bf9d7857b0b476f874

    DPI: 120-640dpi

    Arch: armeabi-v7a, x86

    Файл SHA1-хэша: a8d36dd203c35db86d46831fc01fabccebaeb567

    Размер файла: 83.4 MB

    Что нового:

    Скачать

  • V1.1.1 83.4 MB APK

    Смешарики. Эликсир

    2018-10-25

    Смешарики. Эликсир 1.1.1 (23)

    Обновлено: 2018-10-25

    Uploaded by: Varayut Phetkaew

    Требуется Android: Android 4.1+ (Jelly Bean, API 16)

    Подпись: 621f138320795cbacc1819bf9d7857b0b476f874

    DPI: 120-640dpi

    Arch: armeabi-v7a, x86

    Файл SHA1-хэша: 982adbe998141ad0dac73a1aff73e31cce033274

    Размер файла: 83.4 MB

    Что нового:

    Скачать

  • V1.1.0 87.9 MB XAPK OBB

    Смешарики. Эликсир

    2018-01-09

    Смешарики. Эликсир 1.1.0 (22)

    Обновлено: 2018-01-09

    Uploaded by: Varayut Phetkaew

    Требуется Android: Android 4.0.3+ (Ice Cream Sandwich MR1, API 15)

    Подпись: 621f138320795cbacc1819bf9d7857b0b476f874

    DPI: 120-640dpi

    Arch: armeabi-v7a, x86

    Файл SHA1-хэша: e92f9f10b40b5042a6c101c22f8cc0e2f42ea479

    Размер файла: 87.9 MB

    Что нового:

    Скачать

  • V1.1.0 22.7 MB APK

    Смешарики. Эликсир

    2018-01-09

    Смешарики. Эликсир 1.1.0 (22)

    Обновлено: 2018-01-09

    Uploaded by: Varayut Phetkaew

    Требуется Android: Android 4.0.3+ (Ice Cream Sandwich MR1, API 15)

    Подпись: 621f138320795cbacc1819bf9d7857b0b476f874

    DPI: 120-640dpi

    Arch: armeabi-v7a, x86

    Файл SHA1-хэша: 3bec5da68d1c117b2715d1febb5d6eb833455a19

    Размер файла: 22.7 MB

    Что нового:

    Скачать

  • V1.0.6 87.1 MB XAPK OBB

    Смешарики. Эликсир

    2016-04-13

    Смешарики. Эликсир 1.0.6 (21)

    Обновлено: 2016-04-13

    Uploaded by: Waitfor You

    Требуется Android: Android 2.3.2+ (Gingerbread, API 9)

    Подпись: 621f138320795cbacc1819bf9d7857b0b476f874

    DPI: 120-640dpi

    Arch: armeabi-v7a, x86

    Файл SHA1-хэша: fdaa1e579601b05dc252e1dbea442971df62de01

    Размер файла: 87.1 MB

    Что нового:

    Скачать

  • V1.0.6 22.0 MB APK

    Смешарики. Эликсир

    2016-04-13

    Смешарики. Эликсир 1.0.6 (21)

    Обновлено: 2016-04-13

    Uploaded by: Waitfor You

    Требуется Android: Android 2.3.2+ (Gingerbread, API 9)

    Подпись: 621f138320795cbacc1819bf9d7857b0b476f874

    DPI: 120-640dpi

    Arch: armeabi-v7a, x86

    Файл SHA1-хэша: 69ec91578f4a7c61ca01bd284528623b35d6f781

    Размер файла: 22.0 MB

    Что нового:

    Скачать

  • V1.0.4 89.5 MB XAPK OBB

    Смешарики. Эликсир

    2016-03-19

    Смешарики. Эликсир 1.0.4 (19)

    Обновлено: 2016-03-19

    Uploaded by: ဆင္ျဖဴရြာက ခ်စ္ေတးတစ္ပုဒ္

    Требуется Android: Android 2.3.2+ (Gingerbread, API 9)

    Подпись: 621f138320795cbacc1819bf9d7857b0b476f874

    DPI: 120-640dpi

    Arch: armeabi-v7a, x86

    Файл SHA1-хэша: 2bd2269b4ad393ddb3693406a2cf14491c6f04fa

    Размер файла: 89.5 MB

    Что нового:

    Скачать

  • V1.0.4 24.3 MB APK

    Смешарики. Эликсир

    2016-03-19

    Смешарики. Эликсир 1.0.4 (19)

    Обновлено: 2016-03-19

    Uploaded by: ဆင္ျဖဴရြာက ခ်စ္ေတးတစ္ပုဒ္

    Требуется Android: Android 2.3.2+ (Gingerbread, API 9)

    Подпись: 621f138320795cbacc1819bf9d7857b0b476f874

    DPI: 120-640dpi

    Arch: armeabi-v7a, x86

    Файл SHA1-хэша: 3c120026fe082cae87aca02e3e18b6990d3ffdee

    Размер файла: 24.3 MB

    Что нового:

    Скачать

  • V1.0.3 86.5 MB XAPK OBB

    Смешарики. Эликсир

    2015-11-20

    Смешарики. Эликсир 1.0.3 (16)

    Обновлено: 2015-11-20

    Uploaded by: Waitfor You

    Требуется Android: Android 2.3.2+ (Gingerbread, API 9)

    Подпись: 621f138320795cbacc1819bf9d7857b0b476f874

    DPI: 120-640dpi

    Arch: armeabi-v7a, x86

    Файл SHA1-хэша: b4cb34e0d1b37730c7e885c069b7423b174e2163

    Размер файла: 86.5 MB

    Что нового:

    Скачать

  • V1.0.3 21.2 MB APK

    Смешарики. Эликсир

    2015-11-20

    Смешарики. Эликсир 1.0.3 (16)

    Обновлено: 2015-11-20

    Uploaded by: Waitfor You

    Требуется Android: Android 2.3.2+ (Gingerbread, API 9)

    Подпись: 621f138320795cbacc1819bf9d7857b0b476f874

    DPI: 120-640dpi

    Arch: armeabi-v7a, x86

    Файл SHA1-хэша: 0ac0bea6af8ec1fd5c140c7eee8c8bf43d6b349c

    Размер файла: 21.2 MB

    Что нового:

    Скачать

Рекомендовать

Идёт поиск…

REST API с Elixir / Phoenix – Учебное пособие для новичков.

Фото Джо Кука на Unsplash

Всем привет, хотя я писал Erlang последние несколько лет, я больше использую его только как язык систем / сетевых служб и оставил Python / Django в качестве основного стека веб-разработки. Несколько месяцев назад я подумал, что мои знания Erlang можно легко применить к языку Elixir, и только что начал использовать его с фреймворком Phoenix для некоторых побочных проектов и наслаждался им как стеком веб-разработки.Я собираюсь начать писать серию руководств о том, чему я научился на этом пути, чтобы вы тоже могли учиться и получать такое же удовольствие, как и я.

В этом руководстве мы напишем простой REST API Книги с сохранением базы данных с использованием PostgreSQL. Требования заключаются в том, чтобы иметь одну конечную точку на / api / books , которая разрешает операции CRUD над ресурсом книг со следующими полями:

  • id – это первичный ключ таблицы, и я предпочитаю UUID целочисленным числам.
  • title – Строка, содержащая название книги.
  • isbn – ISBN книги.
  • описание – Текст, определяющий описание книги или аннотацию.
  • цена – Поплавок с указанием балансовой цены.
  • авторов – Строка, содержащая список авторов, разделенных запятыми.
  • Insert_at – Дата и время, определяющие, когда была создана запись.
  • updated_at – Дата и время, определяющие, когда была обновлена ​​запись.

Предварительные требования

Чтобы продолжить изучение этого руководства по Phoenix REST API, у вас должны быть следующие предварительные требования:

  • Иметь общее представление об Elixir и его синтаксисе. В противном случае https://elixir-lang.org/learning.html предлагает отличные ресурсы для начала.
  • Установите PostgresSQL. Подсказка, Postgres.app на OSX сэкономит вам много времени и головной боли.
  • Наличие установленного редактора / IDE по вашему выбору.
  • Установлен клиент Postman REST или знай свой путь в curl.

Начало работы

Хотя есть много способов установить Elixir, это руководство будет следовать простому пути с использованием OSX в качестве операционной системы и диспетчера пакетов Homebrew для установки. Если вам нужно копнуть глубже или использовать другую операционную систему, официальное руководство по установке Elixir подробно описывает это https://elixir-lang.org/install.html.

Установка Эликсира

  • Обновите пакеты Homebrew до последней версии с помощью $ brew update .
  • Установите Elixir с $ brew install elixir . Это установит последнюю версию Elixir, а также Erlang / OTP, поверх которого работает Elixir.
  • Нам также нужно будет установить Hex, менеджер пакетов Elixir, это можно сделать, запустив $ mix local.hex

Чтобы проверить, все ли установлено правильно, запустите:

Что должно вывести:

  Erlang / OTP 23 [erts-11.0.2] [источник] [64-бит] [smp: 4: 4] [ds: 4: 4: 10] [асинхронные потоки: 1] [dtrace]

Эликсир 1.10.3 (скомпилирован с Erlang / OTP 23)
  

Примечание: Версии могут различаться в зависимости от того, когда вы запускали вышеуказанные команды, поскольку homebrew всегда использует самые последние версии.

Установка Phoenix

Теперь, когда у нас есть Elixir и Erlang, давайте установим сценарий начальной загрузки приложения Phoenix с:

  $ mix archive. Установить шестнадцатеричный phx_new 1.5.3
  

Создание проекта Phoenix

Запустите в своем терминале:

  $ микс phx.new books_api --no-html --no-webpack --binary-id && cd books_api
  

Разъяснил:

  • Вы заметите books_api как новый каталог после выполнения команды, это каталог и имя приложения. Дополнительную информацию см. В документации о структуре каталогов приложения Phoenix.
  • Параметры --no-html --no-webpack укажут Phoenix не создавать файлы HTML и шаблон статических ресурсов.Мы делаем это, потому что нас интересует только API для отдыха.
  • Параметр --binary-id настроит Ecto на использование двоичных идентификаторов (UUID) в схемах базы данных.

Как правило, команды смешивания следуют структуре задача смешивания. Подзадача ARGS , если вы просто запустите задачу смешивания , она покажет доступные подзадачи, а смешанная задача. Подзадача покажет параметры подзадачи. Попробуйте, вы будете поражены тем, насколько хорошо документирован Elixir и насколько он интуитивно понятен.

Настройка базы данных

Прежде чем серьезно заняться нашим приложением, давайте настроим его для подключения к нашей базе данных в целях разработки и тестирования. Для этого нам нужно отредактировать config / dev.exs и config / test.exs в каталоге нашего приложения. Настройки по умолчанию выглядят так:

  # Настроить базу данных
config: books_api, BooksApi.Repo,
  имя пользователя: "postgres",
  пароль: "postgres",
  база данных: "books_api_dev",
  имя хоста: "localhost",
  show_sensitive_data_on_connection_error: истина,
  pool_size: 10
  

Теперь вы можете создать базу данных для среды dev с помощью:

Его также можно удалить с помощью:

Подсказка: Установите переменную среды MIX_ENV перед командами переключения среды Phonenix, т.е.е. MIX_ENV = тестовая смесь ecto.create создаст базу данных для тестовой среды.

Моделирование наших данных

Перед тем, как начать, может быть полезно покопаться в Phoenix Contexts, которые вкратце являются способом изолировать нашу систему на управляемые, независимые части, подумайте о нем как о приложении в Django, например, с лучшей организацией, потому что оно инкапсулирует только данные и бизнес-логику. а не веб-части. В этом случае мы разработаем наше приложение с контекстом Store , в котором на данный момент будет книг , но в будущем может быть авторов в виде отдельной схемы с привязкой к книгам.

Теперь у нас есть все необходимое для начала создания нашей модели базы данных книг (схема в терминологии Elixir Ecto). Для этого запустите в каталоге приложения следующий генератор:

  $ mix phx.gen.context Магазин Книжных книг \
title: string isbn: text: уникальное описание: text price: float авторы: array: string
  

Разъяснил:

Эта команда сгенерирует:

  1. Определение схемы в lib / books_api / store / book.ex , который сопоставляет данные, хранящиеся в базе данных, со структурами данных Elixir и добавляет проверку:
  defmodule BooksApi.Store.Book do
  использовать Ecto.Schema
  импортировать Ecto.Changeset

  @primary_key {: id,: binary_id, autogenerate: true}
  @foreign_key_type: двоичный_ид
  схема "книги" делать
    поле: авторы, {: массив,: строка}
    поле: описание,: строка
    поле: isbn,: строка
    поле: цена,: float
    поле: заголовок,: строка

    отметки времени ()
  конец

  @doc ложь
  def changeset (книга, attrs) делать
    книга
    |> cast (attrs, [: title,: isbn,: description,: price,: авторы])
    |> validate_required ([: title,: isbn,: description,: price,: авторы])
    |> уникальное_ограничение (: isbn)
  конец
конец
  
  1. Контекстный модуль в lib / books_api / store.ex с запросами CRUD по умолчанию для книг. Эти запросы можно настроить в соответствии с вашими потребностями, чтобы отфильтровать данные или добавить дополнительные запросы.
  2. Файл миграции по адресу ** priv / repo / migrations / _create_books.exs **. Миграции, когда мы вводим ограничения и индексы уровня БД по мере необходимости. В отличие от миграций Django, мы не можем генерировать их автоматически из изменений схемы, поэтому их нужно создавать вручную при редактировании схемы БД.
  3. Тесты для сгенерированной схемы и запросов.

Выполняемые миграции

Когда мы будем довольны нашей схемой данных и миграцией, мы можем запустить миграцию с:

Создание конечных точек REST

Теперь, когда у нас готовы схемы БД, давайте создадим ресурс JSON книги.Для этого мы запустим генератор, аналогичный предыдущему, для генерации контекста и схемы:

  $ mix phx.gen.json Магазин Книг \
title: string isbn: text: unique description: text price: float авторы: array: string --no-context --no-schema
  

Разъяснил:

  • Параметры --no-context --no-schema гарантируют, что контекст и схема не будут сгенерированы, поскольку они у нас уже есть с предыдущего шага.

Эта команда генерирует:

  1. Контроллер CRUD для книг по адресу lib / books_api_web / controllers / book_controller.из .
  2. Представление для рендеринга книг JSON в lib / books_api_web / views / book_view.ex .
  3. Контроллер и представление используются в качестве резервной копии для ошибок рендеринга при сбое операции контроллера.
  4. Тесты для конечных точек.

Нам нужно отредактировать резервный контроллер и добавить функцию call / 2 для обработки недопустимых предоставленных данных и возврата ошибки 422:

  # Это предложение обрабатывает недопустимые данные ресурса.
def call (conn, {: error,% Ecto.Набор изменений {}}) делать
    conn
    |> put_status (: unprocessable_entity)
    |> put_view (BooksApiWeb.ErrorView)
    |> render (: "422")
конец
  

Добавление маршрутов

На данный момент у нас есть данные, запросы для управления / запроса данных и контроллеры, представления для управления этими данными через REST. Может все работает? Давайте запустим $ mix test и разберемся.

Ой, мы видим некоторые сбои, похожие на:

  1) тестовое удаление книги удаляет выбранную книгу (BooksApiWeb.BookControllerTest)
     test / books_api_web / controllers / book_controller_test.exs: 90
     ** (UndefinedFunctionError) функция BooksApiWeb.Router.Helpers.book_path / 3 не определена или является частной
     код: conn = delete (conn, Routes.book_path (conn,: delete, book))
     трассировки стека:
       ...
  

Он жалуется на отсутствие функции book_path / 3 . Чтобы исправить это, нам нужно добавить маршрут для сопоставления URL-адреса с контроллером. Добавьте ресурс книг в область : api в lib / books_api_web / router.из :

  ресурсов "/ books", BookController, кроме: [: new,: edit]
  

Запуск тестов снова должен быть зеленым:

  $ смешанный тест
................

Завершено за 0,4 секунды
16 тестов, 0 сбоев

Рандомизировано с семенами 824943
  

Запуск приложения

Теперь попробуем запустить приложение. Для запуска сервера разработки запустите на своем терминале следующую команду:

Или запустите его при наличии Elixir REPL (IEx), доступного с:

  $ iex -S mix phx.сервер
Erlang / OTP 23 [erts-11.0.2] [источник] [64-бит] [smp: 4: 4] [ds: 4: 4: 10] [асинхронные потоки: 1] [hipe] [dtrace]

Компиляция 5 файлов (.ex)
Создано приложение books_api
[информация] Запуск BooksApiWeb.Endpoint с cowboy 2.8.0 на 0.0.0.0:4000 (http)
[информация] Получите доступ к BooksApiWeb.Endpoint по адресу http: // localhost: 4000
Интерактивный эликсир (1.10.3) - нажмите Ctrl + C для выхода (для получения справки введите h () ENTER)
  

Теперь посетите http: // localhost: 4000 в своем веб-браузере, и должна появиться следующая страница:

Обратите внимание, что здесь перечислены URL-адреса ресурса «Книги».

Взаимодействие с API

Итак, теперь используйте URL-адреса книг и вставьте их в Postman, чтобы начать взаимодействие с книжным ресурсом.

Попробуйте посетить http: // localhost: 4000 / dashboard, и появится хорошая системная панель:

Это называется Phoenix LiveDashboard и позволяет вам легко контролировать вашу систему и регистрировать пользовательские метрики приложений, но это тема для другой публикации.

Чтобы не видеть HTML-страницу Phoenix со списком маршрутов (на самом деле 404), вы можете установить debug_errors на false в config / dev.exs , затем перезапустите сервер:

 Конфигурация : books_api, BooksApiWeb.Endpoint,
  # ...
  debug_errors: ложь,
  # ...
  

Теперь посещение http: // localhost: 4000 дает:

  {
    "errors": {
        "деталь": "Не найдено"
    }
}
  

Спасибо за внимание!

Это было все о Phoenix REST API Tutorial, хотя там, кажется, много колдовства и магии генератора, все, что он делает, это автоматизирует генерацию скучных CRUD, чтобы вы могли сосредоточиться на своей важной бизнес-логике, весь сгенерированный код очень явный и может быть изменен в соответствии с вашими потребностями и стилем кода.Я надеюсь, что вы сочли это полезным, и этот пост побуждает вас попробовать Phoenix хотя бы ради развлечения. Исходный код этого простого проекта находится на моем Github: https://github.com/codeadict/phx_books_api.

Создайте и протестируйте невероятно быстрый JSON API с Phoenix, фреймворком Elixir

Примечание. Это руководство было написано для Phoenix 0.10. Его части могут перестать работать, если вы используете более новую версию. Если вы используете более новую версию Phoenix, ознакомьтесь с обновленным сообщением в блоге.

Давайте создадим JSON API, который обслуживает список контактов.Мы напишем это с использованием Elixir и Phoenix 0.10. Феникс – это фреймворк, написанный на Elixir, который призван сделать запись в Интернете быстрой и малой задержкой. как можно более приятные приложения. Это не будет проходить установку Эликсир или Феникс. Увидеть Гиды Феникса для начала.

Почему Эликсир и Феникс?

Erlang – это Ferrari, завернутый в металлический лист старого загонщика. Она имеет огромная мощность, но многим это кажется уродливым. Он использовался WhatsApp поддерживает миллиарды подключений, но многим людям сложно незнакомый синтаксис и отсутствие инструментов.Эликсир исправляет это. Он построен на базе Erlang, но имеет красивый и приятный синтаксис с такими инструментами, как смешивание чтобы помочь создавать, тестировать и эффективно работать с приложениями.

Phoenix построен на основе Elixir для создания веб-приложений с очень малой задержкой в окружающая среда, которая по-прежнему приятна. Молниеносно быстрые приложения и приятные среды разработки больше не исключают друг друга. Эликсир и Феникс дает вам обоих. Время отклика в Phoenix часто измеряется в микросекунд вместо миллисекунд.

Теперь, когда мы обсудили, почему мы можем захотеть что-то встроить в этот framework, давайте что-нибудь построим!

Написание теста

См. Начало работы на веб-сайте Phoenix, чтобы узнать, как создать новое приложение под названием HelloPhoenix . Для этого упражнения мы будем использовать Phoenix 0.10.0.

Теперь, когда у вас есть приложение Phoenix, давайте начнем с написания теста. Давайте создадим файл по адресу test / controllers / contact_controller_test.exs

.
  defmodule HelloPhoenix.ContactControllerTest делать
  используйте ExUnit.Case, async: false
  использовать Plug.Test
  псевдоним HelloPhoenix.Contact
  псевдоним HelloPhoenix.Repo
  псевдоним Ecto.Adapters.SQL

  настройка делать
    SQL.begin_test_transaction (репо)

    on_exit fn ->
      SQL.rollback_test_transaction (репо)
    конец
  конец

  test "/ index возвращает список контактов" do
    contacts_as_json =
      % Свяжитесь с {имя: "Гамбо", телефон: "(801) 555-5555"}
      |> Repo.insert
      |> List.wrap
      |> Poison.encode!

    response = conn (: get, "/ api / contacts") |> send_request

    утверждать ответ.статус == 200
    assert response.resp_body == contacts_as_json
  конец

  defp send_request (conn) делать
    conn
    |> put_private (: plug_skip_csrf_protection, истина)
    |> HelloPhoenix.Endpoint.call ([])
  конец
конец
  

Мы пишем функцию setup , чтобы обернуть наши вызовы Ecto в транзакцию, которая будет убедитесь, что наша база данных всегда пуста, когда мы запускаем наши тесты.

Сам тест делает то, что вы ожидаете. используйте Plug.Test дает нам доступ к функции conn / 2 для создания тестовых соединений.В нашем тесте мы вставьте новый контакт, оберните его списком и затем закодируйте. После этого мы создать новое соединение и отправить запрос. Мы утверждаем, что ответ был успешным и что тело содержит список контактов, закодированный как JSON.

Запустите mix test , и мы увидим ошибку HelloPhoenix.Contact .__ struct __ / 0 is undefined, не удается расширить struct HelloPhoenix.Contact . Это означает, что мы не пока создал нашу модель. Давайте использовать Ecto для подключение к базе данных Postgres.

Создание наших баз данных

Ecto использует репозиторий для сохранения и извлечения данных из базы данных. Phoenix уже поставляется с настроенным репозиторием и конфигурацией по умолчанию. Делать убедитесь, что ваше имя пользователя и пароль Postgres верны в config / dev.exs и конфиг / test.exs .

Давайте посмотрим, какие новые задачи mix мы получим от Ecto, запустив mix -h | grep ecto .

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

  # Это создаст вашу базу данных разработчика
$ mix ecto.create
# Это создаст вашу тестовую базу данных
$ env MIX_ENV = тестовая смесь ecto.create
  

Добавление модели контакта

Давайте добавим схему для Contact по адресу web / models / contact.ex .

  defmodule HelloPhoenix.Свяжитесь с нами
  использовать Ecto.Model

  схема "контакты" делать
    поле: имя
    поле: телефон

    отметки времени
  конец
конец
  

Далее мы создадим миграцию с mix ecto.gen.migration create_contacts . В вновь созданная миграция, напишите это:

  defmodule HelloPhoenix.Repo.Migrations.CreateContacts сделать
  использовать Ecto.Migration

  def изменить делать
    создать таблицу (: контакты) сделать
      добавить: имя
      добавить: телефон

      отметки времени
    конец
  конец
конец
  

Тип столбца по умолчанию для миграции Ecto – : строка . Чтобы увидеть что еще вы можете сделать, проверьте Ecto.Migration docs.

Теперь запустите mix ecto.migrate , чтобы создать новую таблицу, и еще раз для тестирования. MIX_ENV = тестовая смесь экто.мигрировать .

Добавление маршрутов и контроллера

Давайте перейдем к конечной точке API с маршрутом, который будет выглядеть как / api / contacts .

  # В нашем web / router.ex
defmodule HelloPhoenix.Router do
  используйте Phoenix.Router

  конвейер: api do
    plug: принимает, ["json"]
  конец

  область "/ api", HelloPhoenix делать
    pipe_through: api

    ресурсы "/ контакты", ContactController
  конец
конец
  

Если вы переходите с Rails, вы заметите, что / api / contacts.json будет Результат 404 не найден. Ожидается, что вы зададите соответствующий запрос заголовок. В крайнем случае можно сделать / api / contacts? Format = json , но это не так рекомендуемые. Параметр конечного формата не был добавлен по соображениям производительности и потому, что заголовки HTTP уже включают эту функцию.

Теперь, если мы запустим mix test , мы увидим, что нам все еще нужен ContactController .

  ** (UndefinedFunctionError) неопределенная функция: HelloPhoenix.ContactController.init / 1 (модуль HelloPhoenix.ContactController недоступен)
  

Давайте создадим наш контроллер по адресу web / controllers / contact_controller.ex

  defmodule HelloPhoenix.ContactController do
  используйте HelloPhoenix.Web,: контроллер
  псевдоним HelloPhoenix.Repo
  псевдоним HelloPhoenix.Contact

  вилка: действие

  def index (conn, _params) делать
    contacts = Repo.all (Контакты)
    render conn, контакты: контакты
  конец
конец
  

Сначала удостоверимся, что получили все Контакты с репо .все (Контакты) . Тогда мы рендеринг JSON с помощью Phoenix.Controller.render / 2 . Функция автоматически импортированный, когда мы вызываем , используйте HelloPhoenix.Web,: controller . Проверить web / web.ex , чтобы увидеть, что еще импортируется.

Если мы запустим mix test , наши тесты еще не пройдут.

  ** (UndefinedFunctionError) неопределенная функция: HelloPhoenix.ContactView.render / 2 (модуль HelloPhoenix.ContactView недоступен)
  

Нам нужно представление для рендеринга нашего JSON.

Рендеринг нашего JSON с представлением

Представления определяют, как выводить наш JSON. Сейчас это довольно просто, но в в будущем мы сможем изменить то, что мы отправляем, в зависимости от того, разрешения например.

Давайте создадим файл в web / views / contact_view.ex

  defmodule HelloPhoenix.ContactView do
  используйте HelloPhoenix.Web,: просмотр

  def render ("index.json",% {contacts: contacts}) делать
    контакты
  конец
конец
  

Это будет использовать сопоставление с образцом для установки, а затем вернет контактов .Феникс автоматически закодирует массив контактов в JSON. Вы можете использовать это view, чтобы настроить представление JSON, но мы рассмотрим это в позже пост.

На этом этапе, когда вы запустите mix test , все тесты должны пройти.

Очистка

Давайте посмотрим HelloPhoenix.Web в web / web.ex , чтобы немного очистить наше приложение. более. Если мы откроем этот файл, мы увидим, что функция контроллера уже имеет псевдоним для HelloPhoenix.Репо .

  def контроллер делать
    цитата делать
      # Автоматически сгенерировано - импортирует все макросы и функции, которые нужны контроллеру.
      использовать Phoenix.Controller

      # Автоматически вставлено - приложение было создано с псевдонимом Repo для удобства.
      псевдоним HelloPhoenix.Repo

      # Это импортирует помощники маршрутизатора, чтобы вы могли генерировать такие пути, как
      # `api_contacts_path (conn)`
      импортировать HelloPhoenix.Router.Helpers
    конец
  конец
  

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

Давайте воспользуемся ExUnit.CaseTemplate чтобы немного очистить наши тесты. В test / test_helper.exs

  # Добавьте это выше `ExUnit.start`
defmodule HelloPhoenix.Case do
  используйте ExUnit.CaseTemplate
  псевдоним Ecto.Adapters.SQL
  псевдоним HelloPhoenix.Repo

  настройка делать
    SQL.begin_test_transaction (репо)

    on_exit fn ->
      SQL.rollback_test_transaction (репо)
    конец
  конец

  используя do
    цитата делать
      псевдоним HelloPhoenix.Repo
      псевдоним HelloPhoenix.Contact
      используйте Plug.Тестовое задание

      # Не забудьте изменить это значение с `defp` на` def`, иначе его нельзя будет использовать в вашем
      # тестов.
      def send_request (conn) делать
        conn
        |> put_private (: plug_skip_csrf_protection, истина)
        |> HelloPhoenix.Endpoint.call ([])
      конец
    конец
  конец
конец
  

Добавление кода к с использованием сделает эти функции и псевдонимы доступными в каждый тест. Таким образом, мы можем удалить send_request / 1 и другие псевдоним из нашего теста и замените его на , используя HelloPhoenix.Корпус

  defmodule HelloPhoenix.ContactControllerTest do
  используйте HelloPhoenix.Case, async: false
  # Мы удалили другие псевдонимы, так как они уже включены в
  # `HelloPhoenix.Case`. Мы также удалили макрос `setup`.

  test "/ index возвращает список контактов" do
    contacts_as_json =
      % Свяжитесь с {имя: "Гамбо", телефон: "(801) 555-5555"}
      |> Repo.insert
      |> List.wrap
      |> Poison.encode!

    response = conn (: get, "/ api / contacts") |> send_request

    утверждать ответ.статус == 200
    assert response.resp_body == contacts_as_json
  конец

  # Мы также удалили определение функции для `send_request / 1`
конец
  

Это обертка

Теперь вы узнали, как создать и протестировать Phoenix JSON API. Мы также узнали, как очистить наши файлы и упростить использование наших модулей в другие контроллеры и тесты в будущем с использованием HelloPhoenix.Web и ExUnit.CaseTemplate . Теперь вы можете развернуть это приложение на Heroku с помощью Elixir. buildpack.

Создание документации по API без усилий из кода Phoenix / Elixir – Часть 1

Эта статья посвящена фактам и технологиям, которые позволяют эффективно и без усилий создавать красивую документацию по API прямо из кода Phoenix. Phoenix – это продуктивная сборка веб-фреймворка на Elixir. Elixir – это динамический функциональный язык, разработанный для создания масштабируемых и поддерживаемых приложений, использующих Erlang VM. Я знаю, что много новых терминов, но не волнуйтесь.В конце концов мы собираемся работать с контроллерами и тестами, и все мы знаем значение этих двух терминов.

Чтобы дать вам краткий обзор того, что мы собираемся делать: мы собираемся использовать библиотеку phoenix_swagger для генерации файла swagger непосредственно из наших контроллеров. Затем мы собираемся использовать библиотеку под названием bureaucrat, которая потребляет этот файл swagger, запускает тесты вашего контроллера и генерирует файл уценки, содержащий информацию из обоих (макросы + тесты). Наконец, мы собираемся использовать slate, который является средством визуализации статической документации API, наполнить его сгенерированным файлом разметки и сгенерировать из него красивую HTML-документацию.

PhoenixSwagger

PhoenixSwagger – это библиотека, которая обеспечивает отличную интеграцию с веб-фреймворком Phoenix. В настоящее время библиотека поддерживает спецификацию OpenAPI версии 2.0 (OAS). Версия 3.0 OAS пока не поддерживается. Чтобы использовать PhoenixSwagger с приложением Phoenix, просто добавьте его в свой список зависимостей в файле mix.exs :

 
def deps do
  [
    {: phoenix_swagger, "~> 0,8"},
    {: ex_json_schema, "~> 0.5 "} # необязательно
  ]
конец
 
 

ex_json_schema – это необязательная зависимость PhoenixSwagger, но она нам понадобится позже, поэтому, пожалуйста, установите и ее. Теперь запустите mix deps.get .

Затем добавьте запись конфигурации в приложение Phoenix, указав имя выходного файла, модули маршрутизатора и конечной точки, используемые для создания файла swagger:

 
config: my_app,: phoenix_swagger,
  swagger_files:% {
    "Priv / static / swagger.json "=> [
      роутер: MyAppWeb.Router,
      конечная точка: MyAppWeb.Endpoint
    ]
  }

 
 

Вы также можете настроить PhoenixSwagger для использования Jason в качестве библиотеки JSON. Я настоятельно рекомендую сделать это, поскольку Jason – это библиотека JSON по умолчанию для Phoenix, и кажется, что он выигрывает битву за принятие над Poison в качестве основной библиотеки JSON Elixir.

 
конфигурация: phoenix_swagger, json_library: Джейсон
 
 

Теперь нам нужно создать контур вашего документа swagger.Представьте себе схему как общую информацию о вашем API, имени, версии, TOS, определениях безопасности и т. Д. Схема реализована как карта, возвращаемая функцией swagger_info / 0 , определяющей ваш модуль Router .

 
defmodule MyApp.Router делать
  используйте MyApp.Web,: router


  конвейер: api do
   plug: принимает, ["json"]
  конец

  область "/ api", MyApp do
    pipe_through: api
    ресурсы "/ users", UserController
  конец

  def swagger_info сделать
    % {
      схемы: ["http", "https", "ws", "wss"],
      Информация: %{
        версия: «1.0 ",
        title: "MyAPI",
        описание: "Документация по API для MyAPI v1",
        termsOfService: "Открыто для всех",
        контакт: %{
          название: "Владимир Горей",
          электронная почта: "[email protected]"
        }
      },
      securityDefinitions:% {
        На предъявителя:% {
          тип: "apiKey",
          имя: "Авторизация",
          описание:
          "API-токен должен быть предоставлен через заголовок` Authorization: Bearer `",
      in: "заголовок"
        }
      },
      потребляет: ["application / json"],
      производит: ["application / json"],
      теги: [
        % {name: "Пользователи", описание: "Пользовательские ресурсы"},
      ]
    }
  конец
конец

 
 

См. Спецификацию объекта Swagger для получения дополнительной информации, которая может быть включена.Теперь выполните следующую команду, и файл спецификации swagger ( swagger.json ) будет создан в каталоге ./priv/static/ .


$ mix phx.swagger.generate

 

Конечно, на данный момент сгенерированный файл спецификации swagger содержит только информацию, предоставленную функцией swagger_info / 0 внутри нашего модуля Router. Чтобы придать ему реальную ценность, мы должны украсить наши действия контроллера макросами PhoenixSwagger. Я не буду вдаваться в подробности, как это сделать.Документация phoenix_swagger делает более адекватную работу по правильному созданию этих макросов.


импортировать Plug.Conn.Status, только: [код: 1]
использовать PhoenixSwagger

swagger_path: индекс делать
  получить ("/ пользователи")
  описание («Список пользователей»)
  ответ (код (: ok), «Успех»)
конец

def index (conn, _params) делать
  users = Repo.all (Пользователь)
  render (conn,: index, users: users)
конец

 

Этот пример демонстрирует действительно простой пример декораций.Для более сложных макросов обратитесь к документации phoenix_swagger. Также обратите внимание, что вместо числового кода HTTP в макросе ответа я использую функцию code (: ok), которая преобразует атом: ok в число 200. Это мое личное соглашение. Я бы предпочел читать атомы с явным значением, чем числовые коды HTTP, где мне всегда нужно какое-то время думать, что представляет собой этот код.

К сожалению, я столкнулся с двумя проблемами при украшении моих контроллеров макросами PhoenixSwagger.

1.) Совместное использование общих схем

PhoenixSwagger не имеет идиоматического решения, как совместно использовать общие схемы. Схемы – это декларативные описания структур данных, возвращаемых конечной точкой. Чтобы определить, что на самом деле представляет собой общая схема :

Общая схема – это схема, используемая более чем в одном контроллере.

Это очень важно помнить. Если вы хотите, чтобы ваши схемы были СУХИМИ, вам нужно придумать способ, как добиться совместного использования вашей схемы между контроллерами.По необходимости я должен был найти решение. Сначала я создал проблему в репозитории PhoenixSwagger, а затем предоставил запрос на вытягивание (PR) с полной документацией предложения / решения. Мы все еще сотрудничаем с автором библиотеки в запросе на слияние, чтобы предложить лучшие решения, но статус-кво предложения таков:

Вот как вы бы условно определяли схемы для вашего контроллера:


  def swagger_definitions сделать
  % {
    Пользователь:
      swagger_schema делать
        title ("Пользователь")
        description («Пользователь приложения»)

        свойства делают
          name (: строка, "Имя пользователя", обязательно: true)
        конец
      конец
  }
конец

 

Вот как вы должны определить схемы для вашего контроллера с общей поддержкой схемы :


  def swagger_definitions сделать
  create_swagger_definitions (% {
    Пользователь:
      swagger_schema делать
        title ("Пользователь")
        description («Пользователь приложения»)

        свойства делают
          name (: строка, "Имя пользователя", обязательно: true)
        конец
      конец
  })
конец

 

Различие тонкое, но очень важное.Вместо возврата карты мы вызываем функцию create_swagger_definitions / 1 , которая возвращает схемы, предоставленные как единственный аргумент функции, объединенной с общими схемами. Дополнительную информацию о том, как это работает, можно найти в упомянутом выше PR.

Примечание: фактическое решение может измениться до фактического объединения Pull Request

2.) Нет поддержки вложенных ресурсов Phoenix.

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

Модуль маршрутизатора


ресурсы "/ группы", только GroupController:
[: show] делать
ресурсы "/ users", только UserController:
[:показатель]
конец

 

Модуль UserController


swagger_path: индекс делать
  получить ("/ groups / {group_id} / users")
  описание («Список пользователей, относящихся к группе»)
  ответ (код (: ok), «Успех»)
конец

def index (conn,% {"group_id" => group_id}) делать
  ...корпус ...
конец

# против

swagger_path: индекс делать
  получить ("/ пользователи")
  описание («Список пользователей»)
  ответ (код (: ok), «Успех»)
конец

def index (% Plug.Conn {} = conn, params) делать
  ... корпус контроллера ...
конец

 

Как видите, мы используем сопоставление с образцом, чтобы сопоставить соответствующее действие контроллера с сопоставлением маршрутизатора. И в этом проблема. PhoenixSwagger не может создавать макросы для одноименного действия контроллера (: index).Первый макрос всегда будет соответствовать действию: index.


Первое, что я сделал, когда узнал, что это может быть проблемой, я создал проблему в репозитории PhoenixSwagger. К сожалению, я не мог дождаться, пока кто-нибудь предоставит мне обходной путь, поэтому я подключил остальные клетки мозга, которые у меня были сегодня вечером, и сам придумал несколько обходных путей. Один обходной путь казался многообещающим, поэтому я продолжил над ним работать, и в конечном итоге это стало рабочей теорией. Конечно, пришлось пойти на компромисс.Мне пришлось избавиться от вложенных ресурсов в моем модуле маршрутизатора, но мне не нужно было трогать файлы контроллера, что было большой победой. Магия заключается в макросе defdelegate Elixir.

Модуль маршрутизатора


ресурсы "/ группы", только GroupController: [: show]
получить "/ groups /: group_id / users", UserController,: index_by_group

 

Модуль UserController


defdelegate index_by_group (conn, params),
  кому: UserController,
  как:: индекс

swagger_path: index_by_group do
  получить ("/ groups / {group_id} / users")
  описание («Список пользователей, относящихся к группе»)
  ответ (код (: ok), «Успех»)
конец

def index (conn,% {"group_id" => group_id}) делать
  ...корпус ...
конец

# против

swagger_path: индекс делать
  получить ("/ пользователи")
  описание («Список пользователей»)
  ответ (код (: ok), «Успех»)
конец


def index (% Plug.Conn {} = conn, params) делать
   ... корпус контроллера ...
конец

 

defdelegate позволяет мне создать локальный псевдоним для одной из функций: index, и макрос PhoenixSwagger использует это делегирование. Таким образом, PhoenixSwagger считает, что у него есть два разных названия действий контроллера.Как я упоминал ранее, вы больше не можете использовать вложенные ресурсы, но вложенность была ценой, которую я был готов принять. Для получения дополнительной информации о вложенных ресурсах Phoenix и PhoenixSwagger, альтернативном решении или любой другой важной информации, пожалуйста, посмотрите выпуск GitHub.

Все проблемы на данный момент решены, давайте снова запустим следующую команду, и ваш вновь сгенерированный файл спецификации swagger будет содержать всю информацию, определенную в макросах контроллера, переведенную в представление JSON.


$ mix phx.чванство. генерировать

 

Теперь в рукаве библиотеки PhoenixSwagger есть последний изящный трюк. Библиотека включает плагин со всеми статическими ресурсами, необходимыми для размещения SwaggerUI из вашего приложения Phoenix. Добавьте в свой маршрутизатор область видимости и перенаправляйте все запросы на SwaggerUI:


область действия "/ api / swagger" делать
  вперед "/", PhoenixSwagger.Plug.SwaggerUI,
    otp_app:: my_app,
    swagger_file: "swagger.json"
конец

 

Запустите сервер с микс phx.server и перейдите к localhost: 4000 / api / swagger. SwaggerUI должен отображаться с загруженным файлом спецификации swagger.

Чтобы получить доступ к файлу спецификации swagger без SwaggerUI, просто перейдите по следующему URL-адресу: localhost: 4000 / api / swagger / swagger.json.

Теперь вы понимаете основные возможности и ограничения библиотеки PhoenixSwagger. Если вы попали сюда, вы уже знаете, как его использовать и создавать документацию по API прямо из вашего кода. Лично мне макросы PhoenixSwagger помогают, когда мне нужно быстро понять, что делает контроллер и какие коды состояния он возвращает, не читая код контроллера, а просто глядя на макросы контроллера.

Единственное, что сейчас отсутствует, – это отсутствие примеров вызовов API (пар запрос / ответ) в нашем сгенерированном файле спецификации swagger для каждого действия контроллера Phoenix, которое мы украсили макросами. Да, SwaggerUI позволит нам делать фактические HTTP-запросы к нашему API, но этого недостаточно. И именно здесь на помощь приходит бюрократ. Он легко интегрируется в наше существующее решение. Но это история для другого раза и для второй части этой серии.

Совет 1

PhoenixSwagger поддерживает x-nullable , чтобы разрешить свойства схемы или параметры запроса быть обнуляемыми:


last_modified (: string, "Дата и время последней модификации видеофайла",
  формат: "дата-время",
  "x-nullable": истина
)

 

Совет 2

Избегайте использования пары фактического типа и атома : null , чтобы указать, что свойство схемы или параметр запроса допускают значение NULL.Вместо этого используйте x-nullable, упомянутый в совете 1. Поля, определенные таким образом, будут вызывать ошибки при проверке файла спецификации swagger в редакторе swagger.


      
# Не делай этого
last_modified ([: string,: null], "Дата и время последней модификации видеофайла",
  формат: "дата-время"
)

 

Если вам понравилась эта статья, вы можете подписаться на меня в Твиттере: @vladimirgorej

Легко создавайте документацию по API из кода серии Phoenix / Elixir:

  • Часть 1: Phoenix Swagger
  • Часть 2: Бюрократ – Документация API по результатам испытаний
  • Часть 3: Slate – Красивая статическая HTML-документация

Создание API GraphQL с использованием Elixir Phoenix и Absinthe

GraphQL – это новая шумиха в области технологий API.Мы создаем и используем REST API в течение некоторого времени и недавно начали слышать о GraphQL. GraphQL обычно описывается как технология API, ориентированная на интерфейс, поскольку она позволяет разработчикам интерфейса запрашивать данные более простым способом, чем когда-либо прежде. Целью этого языка запросов является формулирование клиентских приложений, сформированных в инстинктивном и настраиваемом формате, для отображения их предварительных требований к данным, а также взаимодействий.

Phoenix Framework работает на Elixir, построенном на основе Erlang.Основное преимущество Elixir – масштабирование и параллелизм. Phoenix – это мощный и производительный веб-фреймворк, который не ставит под угрозу скорость и удобство обслуживания. Phoenix имеет встроенную поддержку веб-сокетов, что позволяет создавать приложения в реальном времени.

  1. Elixir & Erlang: Phoenix построен на основе этих
  2. Phoenix Web Framework: используется для написания серверного приложения. (Это малоизвестный и легкий фреймворк в elixir)
  3. Absinthe: Библиотека GraphQL, написанная для Elixir, используемая для написания запросов и мутаций.
  4. GraphiQL: Идея GraphQL на основе браузера для тестирования ваших запросов. Считайте, что это похоже на то, что Postman используется для тестирования REST API.

Приложение, которое мы будем разрабатывать, представляет собой простое приложение для блога, написанное с использованием Phoenix Framework с двумя схемами User и Post, определенными в Accounts и Blog соответственно. Мы разработаем приложение для поддержки API, связанных с созданием и управлением блогами. Предполагая, что у вас установлены Erlang, Elixir и mix.

С чего начать:

Сначала мы должны создать веб-приложение Phoenix, используя следующую команду:

CODE: https: // gist.github.com/velotiotech/894

dd2cec11a98653b66d64dd72.js

–no-brunch – не создавать файлы бранча для создания статических активов. При выборе этого параметра вам нужно будет вручную обрабатывать зависимости JavaScript при создании приложений HTML.

• – -no-html – не генерировать представления HTML.

Примечание : Поскольку мы собираемся в основном работать с API, нам не нужны никакие веб-страницы, представления HTML и поэтому аргументы команды и

Зависимости:

После создания проекта нам нужно добавить зависимости в смеси .exs , чтобы сделать GraphQL доступным для приложения Phoenix.

КОД

: https://gist.github.com/velotiotech/f7eea6933f9fbb482ad07d4e80c16573.js

Структурирование приложения:

Мы можем использовать следующие компоненты для разработки / структурирования нашего приложения GraphQL:

  1. GraphQL Schemas зайти внутрь lib / graphql_web / schema / schema.ex. Схема определяет ваши запросы и изменения.
  2. Пользовательские типы : Ваша схема может включать некоторые настраиваемые свойства, которые должны быть определены внутри lib / graphql_web / schema / types.ex

Resolver : мы должны написать соответствующую функцию Resolver, которая обрабатывает бизнес-логику и должна быть сопоставлена ​​с соответствующим запросом или изменением. Решатели должны быть определены в их собственных файлах. Мы определили его в папке lib / graphql / accounts / user_resolver.ex и lib / graphql / blog / post_resolver.ex .

Также нам нужно обновить маршрутизатор, чтобы иметь возможность делать запросы с использованием клиента GraphQL в lib / graphql_web / router.ex , а также необходимо создать конвейер GraphQL для маршрутизации запроса API, который также входит в lib / graphql_web / router.ex :

КОД: https://gist.github.com/velotiotech/333fc99bfc0525469ac028f2f1d08c4e42.js

9003 9001 Написание запросов GraphQL:

Давайте напишем несколько запросов graphql, которые можно считать эквивалентными запросам GET в REST. Но прежде чем переходить к запросам, давайте взглянем на схему GraphQL, которую мы определили, и ее эквивалентное сопоставление преобразователя:

CODE: https: // gist.github.com/velotiotech/dc1fed4d21e383e4a7203df36060e18f.js

Как видно выше, мы определили четыре запроса в схеме. Давайте выберем запрос и посмотрим, что в нем входит:

КОД: https://gist.github.com/velotiotech/d9f1864f188ce314aa7b14bf148d428f.js

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

  1. arg (:,) : определяет ненулевой входящий строковый аргумент, то есть адрес электронной почты пользователя для нас.
  2. Graphql.Accounts.UserResolver.find / 2 : функция преобразователя, отображаемая через схему, которая содержит основную бизнес-логику для получения пользователя.
  3. Accounts_user : пользовательский тип, определенный внутри lib / graphql_web / schema / types.ex следующим образом:

КОД: https://gist.github.com/velotiotech/200a6d81d4426fc02b3efe847081ad35.

Нам нужно написать отдельную функцию распознавателя для каждого определяемого нами запроса. Перейдем к функции резолвера для accounts_user, которая присутствует в lib / graphql / accounts / user_resolver.ex file:

КОД: https://gist.github.com/velotiotech/66182707c22f13532d3290d3aaca15cb.js

Эта функция используется для вывода списка всех пользователей или получения определенного пользователя с использованием адреса электронной почты. Теперь запустим его с помощью браузера GraphiQL. У вас должен быть сервер, работающий на порту 4000. Для запуска сервера Phoenix используйте:

КОД: https://gist.github.com/velotiotech/fc822fc705c6130635f76af503f5db3d.js

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

Выше мы получили поля идентификатора, электронной почты и имени, выполнив запрос accountUser с адресом электронной почты.GraphQL также позволяет нам определять переменные, которые мы покажем позже при написании различных мутаций.

Давайте выполним еще один запрос, чтобы перечислить все сообщения в блоге, которые мы определили:

Написание мутаций GraphQL:

Давайте напишем несколько мутаций GraphQl. Если вы поняли, как пишутся запросы graphql, мутации намного проще и похожи на запросы, и их легко понять. Он определяется в той же форме, что и запросы с функцией распознавателя. Мы напишем следующие различные мутации:

  1. create_post: – создать новое сообщение в блоге
  2. update_post: – обновить существующее сообщение в блоге
  3. delete_post: – удалить существующее сообщение в блоге

Мутация выглядит следующим образом:

КОД: https: // gist.github.com/velotiotech/c39344c524d9c6d2b71440eb983f42dd.js

Давайте запустим некоторые мутации, чтобы создать сообщение в GraphQL:

Обратите внимание, что здесь используется метод POST, а не GET.

Давайте углубимся в функцию мутации обновления:

КОД: https://gist.github.com/velotiotech/746af69881225144095e6aeff7617f19.js

Здесь сообщение об обновлении принимает в качестве входных данных два аргумента: ненулевой идентификатор и параметр сообщения типа update_post_params который содержит обновляемые значения входных параметров.Мутация определена в lib / graphql_web / schema / schema.ex, а значения входных параметров определены в lib / graphql_web / schema / types.ex –

КОД: https://gist.github.com/velotiotech/c0eca85ebbe846f13901efad22688b57. js

Отличие от предыдущих определений типа в том, что он определяется как input_object, а не object.

Соответствующая функция преобразователя определяется следующим образом:

КОД: https://gist.github.com/velotiotech/e9efd0c7c2d71ce4e880887f2f4f2226.js

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

Это все, что вам нужно, чтобы написать базовый сервер GraphQL для любого приложения Phoenix с использованием Absinthe.

Ссылки:

  1. https://www.howtographql.com/graphql-elixir/0-introduction/
  2. https://pragprog.com/book/wwgraphql/craft-graphql-apis-in-elixir-with -absinthe
  3. https://itnext.io/graphql-with-elixir-phoenix-and-absinthe-6b0ffd260094

JSON API с Phoenix 1.4

Эликсир 1,7

Феникс 1,4

Посмотреть исходный код на GitHub


В этом выпуске мы рассмотрим, как мы можем использовать Phoenix, чтобы помочь нам создать JSON API. В настоящее время у нас есть простое приложение Record Store, которое просто перечисляет некоторые популярные альбомы вместе с некоторой информацией о каждом из них. Мы можем просматривать их на нашем сайте, но сейчас мы хотим создать API, чтобы другие разработчики могли иметь доступ к нашим альбомам.

Приступим.Первое, что нам нужно сделать, это открыть наш router.ex , и в конце файла мы увидим, что Phoenix предоставляет область «api», чтобы помочь нам начать работу. Давайте раскомментируем, чтобы мы могли его использовать. Давайте оставим наш api в пространстве имен, поэтому мы добавим Api после TeacherWeb . Это позволит нам поместить наш контроллер api и модули просмотра в каталог «api», что поможет отделить вещи от наших существующих функций. Затем мы добавим наш ресурс альбомов, и наш API будет доступен только для чтения, поэтому мы ограничим действия «показом» и «индексированием».

библиотека / веб-учителя / router.ex
 
...
область "/ api", TeacherWeb.Api do
  pipe_through: api

  ресурсы "/ альбомы", AlbumController, только: [: show,: index]
конец
...
 
 

Теперь, когда мы определили наши новые маршруты, давайте проверим их правильность. Перейдем в командную строку и запустим mix phoenix routes. Это покажет нам все маршруты, которые мы определили для нашего приложения. Внизу мы видим наши маршруты api.

Несмотря на то, что маршрут выглядит хорошо, помощник пути имеет то же имя, что и помощник для наших существующих маршрутов альбомов.

  $ микс phx.routes
Album_path GET / альбомы TeacherWeb.AlbumController: index
Album_path GET / Album /: id TeacherWeb.AlbumController: показать
...
имя_альбома GET / api / album TeacherWeb.Api.AlbumController: index
Album_path GET / api / album /: id TeacherWeb.Api.AlbumController: показать  

Давайте вернемся к маршрутизатору, и мы можем указать имя нашего помощника с как:: api – это добавит к нашим маршрутам api префикса «api».

библиотека / веб-учителя / router.ex
 
...
область "/ api", TeacherWeb.Api, как:: api do
  pipe_through: api

  ресурсы "/ альбомы", AlbumController, только: [: show,: index]
конец
...
 
 

Затем, если мы вернемся в командную строку и снова запустим mix phx.routes , мы увидим, что наши помощники по пути api имеют префикс «api».

  $ микс phx.routes
...
api_album_path GET / api / album TeacherWeb.Api.AlbumController: index
api_album_path GET / api / album /: id TeacherWeb.Api.AlbumController: показать  

Теперь, когда у нас есть маршруты, давайте создадим album_controller.ex для их обработки. Поскольку мы включили префикс «Api», мы сначала создадим каталог «api». Затем мы создадим наш album_controller.ex и определим наш модуль контроллера.

Теперь нам нужно определить два действия для обработки наших двух маршрутов, поэтому давайте создадим функцию «index», взяв наше соединение, и мы можем игнорировать наши params , а затем нам понадобится действие «show».

Давайте реализуем действие «index». Мы хотим, чтобы он возвращал список всех наших альбомов, и чтобы получить все наши альбомы, мы можем использовать функцию Recordings.list_albums . Тогда давайте назовем модуль Recordings псевдонимом, чтобы мы могли использовать его без префикса.

Теперь самый быстрый способ визуализировать наш JSON – это использовать функцию Phoenix.Controller.json . Давайте включим это сюда. Потребуется наше соединение, а затем данные, которые мы хотим использовать в ответе.

Библиотека / Teacher_web / контроллеры / API / album_controller.ex
 
defmodule TeacherWeb.Api.AlbumController делать
  используйте TeacherWeb,: контроллер

  псевдоним Учитель.

  def index (conn, _params) делать
    альбомы = Recordings.list_albums ()
    json (conn,% {данные: альбомы})
  конец

  def show (conn, params) делать

  конец
конец
 
 

Затем мы перейдем в командную строку и запустим наш сервер.

  $ смешанный phx.server
...  

Хорошо, давайте перейдем к нашему маршруту http: // localhost: 4000 / api / Albums.И мы видим, что получаем ошибку: «протокол Jason.Encoder не реализован» для нашей структуры альбома.

Просматривая наше сообщение об ошибке, мы видим, что на самом деле нам есть несколько вариантов решения этой проблемы. Если мы владеем структурой, мы могли бы получить реализацию и указать, какие поля использовать, или мы могли бы кодировать все поля. Если мы не владеем структурой, мы можем использовать функцию Protocol.derive .

Структура альбома принадлежит нам, поэтому давайте воспользуемся первым предложением. получение реализации и указание, какие поля использовать.Мы откроем наш модуль album.ex и добавим производный Jason.Encoder , указав нужные поля: «id» и «title».

библиотека / учитель / записи / альбом.ex
 
defmodule Teacher.Recordings.Album do
  использовать Ecto.Schema
  импортировать Ecto.Changeset
  @derive {Jason.Encoder, только: [: id,: title]}

  ...

конец
 
 

Затем, если мы вернемся в браузер и перезагрузим страницу – отлично, все наши альбомы возвращаются, только с указанными нами полями. Нам практически удалось заставить наш API работать практически в любое время.Пока наша текущая реализация работает, поскольку Phoenix представляет собой платформу MVC, давайте посмотрим, как мы можем использовать представления для визуализации наших данных.

Давайте вернемся к нашему модулю album.ex и удалим наш вызов @derive . Затем давайте откроем наш album_controller.ex и вместо того, чтобы вызывать здесь функцию json , давайте теперь вызовем функцию render , передав в нашем соединении имя шаблона – назовем это «index.json» – и затем мы передадим любые задания, в данном случае наши альбомы.

Библиотека / Teacher_web / контроллеры / API / album_controller.ex
 
...
def index (conn, _params) делать
  альбомы = Recordings.list_albums ()
  рендеринг (conn, "index.json", альбомы: альбомы)
конец
...
 
 

Теперь, когда мы обновили наш контроллер, нам нужно создать представление. Как и в случае с нашим контроллером, сначала мы создадим каталог «api», только теперь мы создадим его внутри «представлений». Затем мы можем создать наш album_view.ex и определить модуль просмотра.

Мы можем определить функцию render и сопоставить шаблон в строке index.json », а также о назначениях, чтобы мы могли получить наши альбомы. Поскольку наши альбомы представляют собой список структур альбомов, Phoenix предоставляет нам функцию render_many , чтобы упростить рендеринг нашей коллекции. Мы назовем render_many , передав в наши альбомы , затем модуль просмотра и, наконец, шаблон, который мы назовем «album.json».

Нам также необходимо определить нашу функцию рендеринга «album.json», что мы можем сделать, создав еще одну функцию рендеринга и сопоставив имя с образцом – «альбом.json »- а затем сопоставление с образцом, чтобы получить наш альбом. И внутри этой функции мы вернем карту атрибутов, которые хотим вернуть в нашем API.

Библиотека / Teacher_web / views / api / album_view.ex
 
defmodule TeacherWeb.Api.AlbumView делать
  использовать TeacherWeb,: просмотр

  def render ("index.json",% {альбомы: альбомы}) делать
    % {data: render_many (альбомы, TeacherWeb.Api.AlbumView, "album.json")}
  конец

  def render ("album.json",% {album: album}) делать
    % {id: album.id,
        исполнитель: album.artist,
        название: альбом.заглавие}
  конец

конец
 
 

Наше представление теперь должно быть настроено для обработки нашего действия «index». Вернемся к браузеру, и если мы снова проверим наш маршрут – отлично, мы видим, что наши данные возвращаются, и мы видим, что наши три поля отображаются для каждого альбома.

Теперь, когда у нас есть действие «index», давайте реализуем наше шоу. Мы вернемся к album_controller.ex и, поскольку нам нужно будет искать альбом по его идентификатору, давайте сопоставим шаблон, чтобы получить «идентификатор» из параметров.Тогда воспользуемся Recordings.get_album! , чтобы получить наш альбом.

В нашем альбоме мы можем снова вызвать функцию render . Только на этот раз мы будем использовать «show.json» и передадим один альбом в качестве заданий.

Библиотека / Teacher_web / контроллеры / API / album_controller.ex
 
...
def show (conn,% {"id" => id}) делать
  альбом = Recordings.get_album! (id)
  render (conn, "show.json", альбом: альбом)
конец
...
 
 

Затем мы перейдем к нашему album_view.ex , и мы реализуем нашу функцию render для обработки нашего нового «show.json», сопоставления шаблонов в нашем сингл «альбоме». В функции рендеринга для нашего «index.json» мы использовали render_many для рендеринга коллекции. Здесь мы можем использовать функцию render_one для рендеринга одного элемента, дав ему наш album , модуль представления, который мы хотим использовать, и, поскольку мы просто хотим отображать те же поля, мы можем использовать тот же рендер «album.json». функция.

Библиотека / Teacher_web / views / api / album_view.бывший
 
...
def render ("show.json",% {album: album}) делать
  % {data: render_one (альбом, TeacherWeb.Api.AlbumView, "album.json")}
конец
...
 
 

Теперь вернемся к браузеру и увидим, что действие index перечисляет наши альбомы. Давайте возьмем идентификатор альбома, а затем запросим действие «показать» API. И здорово – возвращен ожидаемый сингл-альбом. Мы получаем исполнителя и название альбома, но не хватает одной категории альбома.

Давайте откроем наш альбом .ex модуль. И мы можем видеть в нашей схеме, что наш альбом имеет отношение belongs_to с категорией. Давайте обновим наш API, чтобы отображалась и категория альбома.

Теперь мы могли бы просто получить название нашей категории из нашего альбома и отобразить его как поле в нашем альбоме, но что, если бы мы хотели отобразить больше полей из нашей категории? В нашем примере давайте отобразим идентификатор вместе с названием категории.

Вместо фильтрации по нужным здесь полям давайте создадим для использования отдельный модуль CategoryView .Мы создадим новый файл category_view.ex , а затем внутри него определим наш модуль.

Теперь давайте создадим функцию render , и поскольку она будет отображать информацию об одной категории, мы будем использовать тот же шаблон, что и раньше, вызывая этот «category.json», а затем сопоставим с шаблоном, чтобы получить нашу категорию . А внутри функции давайте вернем карту с полями, которые мы хотим отобразить: «id» и «name» категории.

Библиотека / Teacher_web / views / api / category_view.бывший
 
defmodule TeacherWeb.Api.CategoryView делать
  использовать TeacherWeb,: просмотр

  def render ("category.json",% {category: category}) делать
    % {id: category.id,
      name: category.name}
  конец

конец
 
 

Отлично, теперь, когда у нас есть настройка CategoryView , давайте вернемся к нашему album_view.ex и в нашем render для нашего «album.json» давайте добавим нашу категорию. Поскольку альбом принадлежит к одной «категории», мы будем использовать функцию render_one , передав категорию альбома, модуль просмотра – в данном случае CategoryView и, наконец, «category.json », который мы определили.

Библиотека / Teacher_web / views / api / album_view.ex
 
...
def render ("album.json",% {album: album}) делать
  % {id: album.id,
    исполнитель: album.artist,
    название: album.title,
    category: render_one (album.category, TeacherWeb.Api.CategoryView, "category.json")}
конец
...
 
 

Теперь проверим, работают ли наши категории. Вернемся в браузер и перезагрузим страницу. И здорово – мы видим, что данные нашей категории теперь отображаются вместе с нашим альбомом.Давайте также попробуем наш индексный маршрут. В каждом альбоме отображается соответствующая категория.

Как я создал оболочку Elixir для Alpaca API

Это часть серии гостевых постов от членов сообщества Alpaca. На этой неделе Джеймс, инженер-программист @brexHQ, пишет о том, как он создал оболочку Elixir для API Alpaca.

Зачем я построил оболочку для Эликсира

Недавно я сменил работу и начал профессионально писать код на Elixir .

elixir-lang.github.com

Веб-сайт для Elixir

Я чувствую себя достаточно опытным в работе, где большинство вещей настроено и работает, относительно легко разрабатывать и добавлять новые функции. Тем не менее, я хотел получить более полное представление о настройке проекта и инструментов Elixir с нуля. Поэтому я решил начать с простого и попытаться написать оболочку Elixir для стороннего API.

Я подумал, что это будет достаточно простой проект, чтобы начать работу, прежде чем переходить к более сложному проекту.

Недавно я увидел статью о Alpaca , сервисе, который позволяет «торговать с помощью алгоритмов, подключаться к приложениям, создавать сервисы – и все это с помощью API для торговли акциями без комиссии». Поэтому я просмотрел их документацию и еще не увидел обертку для Эликсира. Затем я произвел быстрый поиск в Google и увидел, что один проект Elixir github использует его, но ему было много лет и он был незавершенным.

Видя, что оберток не было, я решил попробовать. Итак, я зарегистрировал учетную запись и начал читать их документацию.Кульминацией этого является “ alpaca_elixir , оболочка Elixir для API Alpaca.

jrusso1020 / alpaca_elixir

Клиентская оболочка Alpaca API Elixir. Участвуйте в разработке jrusso1020 / alpaca_elixir, создав учетную запись на GitHub.

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

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

Внешний API должен позволять бесплатные учетные записи разработчиков

Недавно я хотел создать оболочку для стороннего API. Это не был API, который я использовал сам (по крайней мере, на данный момент), но, поскольку для этого языка еще не было оболочки, я подумал, что попробую.К счастью, эта компания предоставляет тестовую учетную запись и среду песочницы для каждой учетной записи, свободной от…

Создание оболочки API Эликсира

Первое, что мне нужно было сделать, это проверить API Альпака и посмотреть, какие конечные точки нам нужны для поддержки, что было относительно просто. У них есть вся их документация онлайн, и я бы сказал, что в целом она довольно лаконична и RESTful. Общая RESTfulness их API делала его относительно простым в разработке, а с использованием макросов Elixir было невероятно легко быстро писать конечные точки.

RESTfulness большинства API Альпаки предоставил мне интересное решение, так как я использовал Elixir.

Эликсир предлагает возможность писать макросы , или код, который пишет код. Просматривая Lob Elixir API , я заметил, что они использовали макрос для определения модуля ResourceBase , который позволял им в основном определять основные операции RESTful один раз с помощью макроса, а затем вводить этот код в отдельные модули и просто изменять конечные точки, которые быть пораженным.

lob / lob-elixir

Библиотека эликсира для Lob API. Участвуйте в разработке lob / lob-elixir, создав учетную запись на GitHub.

Итак, я решил применить аналогичный подход, основанный на RESTfulness API Альпаки. Это означает, что я мог бы один раз определить базовые операции RESTful CRUD, а затем просто определить модули, которые “ используют ” этот модуль, и настроить все конечные точки. Я включил полный код внизу этого сообщения, но вы также можете увидеть его здесь, на Github.

jrusso1020 / alpaca_elixir

Клиентская оболочка Alpaca API Elixir. Участвуйте в разработке jrusso1020 / alpaca_elixir, создав учетную запись на GitHub.

Это означает, что я фактически тратил большую часть своего времени на написание тестов и документации вместо написания кода. После того, как этот модуль `Resource` был определен, большинство модулей конечных точек были по существу одной строкой кода для` использования Alpaca.Resource, endpoint: “orders”, exclude: [: update] `. Были некоторые конечные точки, которые не вписывались в определение ресурса CRUD, поэтому в этих случаях я реализовал их отдельно, используя уже предопределенный HTTP-клиент.

Благодаря простоте добавления новых конечных точек я смог реализовать все функции Alpaca API V2 менее чем за месяц кодирования в свободное время после работы и по выходным. И, как я уже сказал, большую часть времени я тратил на написание тестов и документации.

Говоря о документации, я считаю, что это основная часть проекта программного обеспечения с открытым исходным кодом и еще один способ, которым Elixir действительно выделяется. Elixir встроил документацию в ваш рабочий процесс разработки, позволяя вам легко определять документацию для ваших модулей и функций, а затем автоматически генерировать ее в HTML-документы со встроенной уценкой.Вы даже можете написать примеры, которые будут запускаться как тесты в вашем наборе тестирования. Затем эта документация автоматически развертывается и размещается вместе с вашим шестнадцатеричным пакетом. Вы можете найти документацию по alpaca_elixir здесь https://hexdocs.pm/alpaca_elixir/AlpacaElixir.html.

Следует отметить, что, очевидно, потоковый API Альпаки не мог быть реализован с использованием того же HTTP-клиента, который я определил для остальных конечных точек HTTP. Однако с помощью библиотеки WebSockex я смог определить модуль потока в Elixir, который выполняет начальную настройку и авторизацию за вас и позволяет разработчикам просто определять свои собственные функции для обработки входящих сообщений.Вы также должны иметь возможность добавить модуль `Alpaca.Stream` в свое дерево наблюдения и запускать его в собственном процессе, что является действительно отличным плюсом Elixir. Вы можете увидеть этот код здесь в репозитории Github. (Небольшое предупреждение, этот код потока не протестирован (на момент написания), и я бы пока не рекомендовал его в производственной среде. Однако я надеюсь получить отзывы от пользователей и исправить любые проблемы, которые могут возникнуть. )

В целом процесс написания Alpaca API Wrapper был довольно простым и понятным из-за отличной документации и практик RESTful, которым следовала команда Alpaca, и выбора использовать Elixir.Еще раз я хотел бы коснуться удивительной разработки команды Alpaca, предлагающей бесплатную полнофункциональную среду песочницы для всех своих пользователей. Это был невероятный выбор, который действительно позволяет им создать вокруг своего продукта большое сообщество разработчиков, что следует делать большему количеству компаний.

Я с нетерпением жду встречи с первыми пользователями моей оболочки Elixir и, пожалуйста, сообщайте о любых возникающих проблемах на Github! Я сделаю все возможное, чтобы исправить их своевременно, но я также всегда открыт для любых участников с открытым исходным кодом, которые хотели бы помочь. Вы можете связаться со мной (Джеймсом) по телефону: https://boredhacking.com/

Все сообщения

Блог, посвященный разработке программного обеспечения, веб-разработке, технологиям и жизни

  defmodule Alpaca.Resource do
  @moduledoc "" "
  Этот модуль использует макрос, чтобы мы могли легко создать базу
  HTTP-методы для ресурса Alpaca. Большинство запросов Альпаки
  общий набор методов для получения определенного ресурса по идентификатору, список
  все ресурсы, обновить ресурс, удалить все ресурсы
  и удалить ресурс по id.Используя этот макрос, мы можем легко
  создавать новые конечные точки API в одной строке кода.
  ### Пример
  `` ''
  defmodule Alpaca.NewResource do
    используйте Alpaca.Resource, конечная точка: "new_resources"
  конец
  `` ''
  В этой единственной строке кода у нас теперь будет `list / 0`,` list / 1`,
  get / 1, get / 2, create / 1, edit / 2, delete_all / 0 и delete / 1
  функции для данного ресурса.
  Вы также можете исключить функции из создаваемых, передав их в виде списка
  атомов с ключевым словом `: exclude` в определении ресурса.### Пример
  `` ''
  defmodule Alpaca.NewResource do
    используйте Alpaca.Resource, конечную точку: "new_resources", исключите: [: delete_all,: delete]
  конец
  `` ''
  Это определение не создаст конечную точку delete_all / 0 или delete / 1 для
  новый ресурс, который вы определили.
  "" "
  defmacro __ using __ (options) do
    endpoint = Keyword.fetch! (параметры,: конечная точка)
    exclude = Keyword.get (параметры,: exclude, [])
    opts = Keyword.get (параметры,: opts, [])

    цитата делать
      псевдоним Alpaca.Client

      если: перечислить без кавычек (исключить) сделать
        @doc "" "
        Функция для вывода списка всех ресурсов из API Альпаки.
        "" "
        @spec list (map ()) :: {: ok, [map ()]} | {: ошибка, карта ()}
        список def (params \\% {}) делать
          Клиент.получить (base_url (), params, unquote (opts))
        конец
      конец

      если: получить в unquote (исключить) сделать
        @doc "" "
        Функция для получения единственного ресурса из API Альпаки.
        "" "
        @spec get (String.t (), map ()) :: {: ok, map ()} | {: ошибка, карта ()}
        def get (id, params \\% {}) делать
          Client.get (resource_url (id), params, unquote (opts))
        конец
      конец

      если: создать в кавычках (исключить) сделать
        @doc "" "
        Функция для создания нового ресурса из API Альпаки.
        "" "
        @spec create (map ()) :: {: ok, map ()} | {: ошибка, карта ()}
        def create (params) делать
          Клиент.сообщение (base_url (), params, unquote (opts))
        конец
      конец

      если: редактировать без кавычек (исключить) делать
        @doc "" "
        Функция редактирования существующего ресурса с помощью Alpaca API.
        "" "
        @spec edit (String.t (), map ()) :: {: ok, map ()} | {: ошибка, карта ()}
        def edit (id, params) делать
          Client.patch (resource_url (id), params, unquote (opts))
        конец
      конец

      если: обновить без кавычек (исключить) сделать
        @doc "" "
        Функция обновления существующего ресурса с помощью API Альпака.
        "" "
        @spec update (String.t (), map ()) :: {: ok, map ()} | {: ошибка, карта ()}
        def update (id, params) делать
          Client.put (url_ресурса (id), params, unquote (opts))
        конец
      конец

      если: delete_all in unquote (исключить) do
        @doc "" "
        Функция для удаления всех ресурсов заданного типа с помощью API Альпака.
        "" "
        @spec delete_all () :: {: ok, [map ()]} | {: ошибка, карта ()}
        def delete_all () делать
          с {: ok, response_body} <- Client.delete (base_url (), unquote (opts)) сделать
            {:Ok,
             Enum.карта (response_body, элемент fn ->
               % {
                 статус: item.status,
                 id: item [: id] || элемент [: символ],
                 ресурс: item.body
               }
             конец)}
          конец
        конец
      конец

      если: удалить в кавычках (исключить) сделать
        @doc "" "
        Функция для удаления единственного ресурса заданного типа с помощью API Альпака.
        "" "
        @spec (delete (String.t ()) ::: ok, {: error, map ()})
        def удалить (id) сделать
          с: ok <- Клиент.удалить (resource_url (id), unquote (opts)) сделать
            :Ok
          конец
        конец
      конец

      @spec base_url :: String.t ()
      defp base_url () делать
        version = Keyword.get (unquote (opts),: version, "v2")

        "/ # {версия} / # {unquote (endpoint)}"
      конец

      @spec url_ресурса (String.t ()) :: String.t ()
      defp resource_url (resource_id), выполните: "# {base_url ()} / # {resource_id}"
    конец
  конец
конец  

Технологии и услуги предлагаются AlpacaDB, Inc.Брокерские услуги предоставляет ООО «Альпака Секьюритиз», член FINRA / SIPC. ООО «Альпака Секьюритиз» является 100-процентной дочерней компанией AlpacaDB, Inc.

Тестирование HTTP API в Elixir

Здесь, в PSPDFKit, мы любим тестирование. С растущим разнообразием продуктов в нашей дорожной карте нет недостатка в разнообразии подходов к тестированию, которые мы используем для проверки правильности работы каждого компонента в любом из наших продуктов.

Один из наших будущих продуктов PSPDF Instant - это полный пакет для обеспечения совместной работы пользователей в реальном времени.Грубо говоря, он состоит из двух компонентов: серверной части, работающей в облаке, и платформы iOS, которая интегрируется в приложение конечного пользователя. Мы используем Elixir в качестве основной технологии для нашей серверной части. В этом посте я покажу вам, как мы подходим к тестированию HTTP API в Elixir с помощью нашей библиотеки Bypass с открытым исходным кодом.

Обзор

Прежде чем мы начнем, нам нужен подопытный. Представим, что у нас есть следующая система, показанная в виде концептуальной схемы на рисунке 1.

Рис. 1. Концептуальная схема системы, которую мы будем тестировать

Рисунок 1 - это упрощенная версия реальной системы аутентификации, которую мы используем. Клиентом может быть мобильное устройство или веб-браузер, который устанавливает соединение WebSocket с нашим мгновенным сервером . Внутри сервера каждый клиент поддерживается процессом Elixir (показан кружком с меткой WS на диаграмме выше). Каждый клиент должен аутентифицироваться после подключения, что выполняется с помощью внешней службы, которую мы назовем Auth server .Обоснование такой схемы простое: Instant - это промежуточный продукт, который организация захочет интегрировать со своими существующими учетными записями пользователей. Следовательно, организация должна иметь возможность настроить Instant таким образом, чтобы он передавал входящие запросы аутентификации через этот внешний сервер аутентификации.

Поток данных во время процесса аутентификации одного клиента выглядит так, как показано на рисунке 2 ниже.

Рисунок 2. Визуализация потока данных между клиентом и сервером аутентификации

Восходящий компонент сервера Instant может сам состоять из пула рабочих, позволяющих нескольким клиентам аутентифицироваться одновременно, но такие детали не имеют отношения к нашей цели здесь.Важно то, что в конечном итоге HTTP-запрос отправляется из процесса Upstream.Worker на сервер Auth, а последний возвращает ответ. Мы собираемся протестировать протокол, который используется для связи с сервером аутентификации.

Наши цели

Мы хотим проверить две вещи:

  1. Компонент восходящего потока отправляет правильные запросы серверу аутентификации.

  2. Компонент Upstream может обрабатывать все возможные ответы от сервера Auth.

Если принять во внимание возможность разрыва соединения, тайм-аутов или даже ошибок, связанных с внутренним состоянием сервера аутентификации (например, статус HTTP 500), диапазон ответов от сервера аутентификации может быть огромным. Однако мы не собираемся рассматривать их все, поскольку мы в первую очередь заинтересованы в том, чтобы наша часть контракта, установленного между двумя конечными точками, была правильно реализована.

Выбор подхода к тестированию

После того, как цели поставлены, нам нужно решить, какой подход мы будем использовать для их достижения.Как и в любом другом деле, есть несколько способов сделать это:

  • Имитация HTTP-клиента, на который полагается Upstream, чтобы вообще не использовать сетевой стек, и возвращать предварительно запеченные ответы сервера.

  • Создайте промежуточный уровень между компонентом восходящего потока и стеком HTTP, а затем протестируйте только этот уровень. Этот подход предполагает, что последующее кодирование / декодирование из HTTP не содержит ошибок (или его можно протестировать отдельно).

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

Первый подход обычно не рекомендуется. Это приводит к хрупким тестам, которые, как правило, слишком сильно зависят от деталей реализации. Хосе Валим очень хорошо осветил эту тему в своем посте «Моки и явные контракты».

У второго подхода есть свои достоинства, однако он также сопряжен с накладными расходами, связанными с необходимостью создания промежуточного уровня только для его тестирования. Этот подход может быть необходим иногда, например, когда требуется поддержка других протоколов, кроме HTTP, для связи с внешней службой.Но для конкретного случая тестирования HTTP API этот подход не так привлекателен, поскольку HTTP уже работает как промежуточный уровень между компонентами.

Мы считаем, что третий подход является наиболее эффективным, поскольку он требует минимальных изменений или вообще не требует изменений в коде приложения и очень гибок при использовании его в составе среды тестирования, такой как ExUnit. Используя Bypass, мы можем заменить внешний сервер Auth имитацией в каждом отдельном тестовом примере, настроить параметр конфигурации и вуаля - компонент Upstream теперь будет отправлять запросы на фиктивный сервер вместо поиска внешней службы.

Ключевым моментом здесь является то, что мы тестируем протокол связи между Instant и Auth. Детали реализации любого из них не повлияют на наши тесты, даже если они изменятся. Например, если в какой-то момент в будущем мы решим использовать другую клиентскую библиотеку HTTP в Instant, нам не нужно будет трогать наши тесты аутентификации. Работая на уровне HTTP, наши тесты реализуют точно такие же пути кода, которые будут использоваться в производственной среде. Создание подобных тестов делает их намного более ценными и дает нам больше уверенности в том, что система в целом будет работать так, как ожидалось.

Использование байпаса

Как это часто бывает, мы собираемся использовать ExUnit в качестве нашей среды тестирования. Базовая настройка байпаса описана в файле Readme. Для аудиовизуального введения я рекомендую посмотреть 3-минутный доклад Мартина Шюррера о начале работы с Bypass, который он представил на последней конференции ElixirConfEU.

Настройка очень проста. Просто добавьте Bypass в список зависимостей проекта в среде : test и запустите его вручную перед запуском тестов.Это гарантирует, что приложение Bypass ’OTP запускается только на этапе тестирования и не используется вообще в других средах. Его код даже не будет включен в выпуск хост-приложения.

После добавления зависимости : bypass мы модифицируем test / test_helper.exs , чтобы он выглядел следующим образом

 ExUnit.start
{: ok, _} = Application.ensure_all_started (: bypass) 

С этого момента любой тестовый пример, который хочет использовать фиктивную конечную точку HTTP, должен вызывать Bypass.откройте и используйте порт, который он выделяет при подключении.

Давайте посмотрим на пример такого тестового случая.

 @prebaked_auth_response% {client_id: "a", user_id: "b"}
@prebaked_auth_response_json JSX.encode! (% {"data" => @prebaked_auth_response})

тест "Authenticate / 4 выполняет POST для / аутентификации" do
  # Подготовить конечную точку HTTP
  bypass = Bypass.open
  upstream = start_upstream (url: "http: // localhost: # {bypass.port}")

  # Установите наши ожидания для этого тестового примера
  Обход.ожидать обхода, fn conn ->
    assert "/ Authenticate" == conn.request_path
    assert "POST" == conn.method
    Plug.Conn.resp (conn, 200, @prebaked_auth_response_json)
  конец

  # Убедитесь, что наш компонент Upstream отправил правильный запрос и
  # правильно обработал ответ
  assert {: ok, @prebaked_auth_response}
      == Upstream.authenticate (восходящий поток, "", @user_agent, @client_info)
конец

# Вспомогательная функция для запуска компонента Upstream в тестовом примере
defp start_upstream (параметры) сделать
  upstream = Upstream.start_link (параметры)
  on_exit fn -> Upstream.stop (восходящий) конец
  вверх по течению
конец 

Первое, что мы делаем, это устанавливаем наше ожидание для HTTP-запроса, выполняемого в этом конкретном тестовом примере. Переменная conn , которая передается анонимной функции, точно такая же, как conn , которую вы получаете в обработчике маршрута в приложении Plug. Он содержит всю информацию, связанную с запросом, и может использоваться для отправки ответа клиенту.

Обратите внимание, что мы можем свободно использовать макросы ExUnit для проверки вещей внутри обработчика запросов.Если какое-либо из утверждений не сработает, мы получим стандартное сообщение об ошибке ExUnit с трассировкой стека, указывающей на точную строку, где произошел сбой.

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

Давайте посмотрим на другой тестовый пример.

 тест «аутентифицировать / 4 обрабатывает сбой и возвращает неавторизованный» do
  bypass = Bypass.open
  upstream = start_upstream (url: "http: // localhost: # {bypass.порт} ")

  Bypass.expect bypass, fn conn ->
    {: ok, body, conn} = Plug.Conn.read_body (conn)
    assert% {"authentication_payload" => "foo"} = JSX.decode! (тело)
    Plug.Conn.resp (conn, 200, JSX.encode! (% {Error:% {cause: "unauthorized"}}))
  конец

  assert {: error,: unauthorized}
      == Upstream.authenticate (восходящий поток, "foo", @user_agent, @client_info)
конец 

Здесь мы проверяем, что передача недопустимой полезной нагрузки аутентификации приводит к ответу неавторизованных , возвращаемому восходящему компоненту, который затем превращает его в кортеж {: error,: unauthorized} .

На что следует обратить внимание:

  • Пока вы открываете новую конечную точку обхода в каждом тестовом случае, она будет изолирована от остальных тестов. Это позволит вам запустить ExUnit с async: true и наслаждаться запуском тестов на невероятных скоростях. Кроме того, не нужно беспокоиться о выключении конечной точки, так как об этом позаботятся вы.

  • Bypass не налагает никаких ограничений на то, что вы можете делать в ваших обработчиках Bypass, это полностью зависит от вас.Если вам нужно протестировать гораздо более крупный HTTP API, вы можете отправить HTTP-запросы к реальной внешней службе, сохранить ее ответы и впоследствии использовать их в обработчиках обхода.

  • Как я упоминал ранее, в код приложения не было внесено никаких изменений. Компонент Upstream ожидает, что URL-адрес конечной точки будет передан ему в качестве параметра конфигурации, и благодаря этому факту мы можем передать ему настраиваемую конечную точку, полученную из Bypass.

Обход вниз и Обход.вверх

Другой аспект работы с HTTP API заключается в том, что сама сеть может быть источником дополнительных условий ошибки. Для солидного изделия рекомендуется заниматься такими случаями. Bypass предлагает способ временно закрыть прослушивающий сокет, а затем снова запустить его по команде. Такой простой механизм позволяет протестировать постепенное восстановление после проблем с подключением.

Давайте посмотрим на пример.

 @upstream_conn_count 4

тест "рабочие становятся доступными, когда сервер подключается к сети после их запуска" делать
  bypass = Обход.открытым
  upstream = start_upstream (url: "http: // localhost: # {bypass.port}")

  Bypass.down (байпас)

  # Сначала убедитесь, что воркер недоступен
  assert {: error,: noconnect} = Upstream.perform (upstream,: ping)

  Bypass.up (байпас)
  wait_for_queue_len (@upstream_conn_count)

  # Убедитесь, что воркер теперь доступен
  assert: ok = Upstream.perform (восходящий поток,: ping)
конец 

Здесь мы тестируем способность нашего восходящего компонента справляться с сбоями сети. Когда внешний сервер недоступен, мы получаем ошибку : noconnect от Upstream.Как только он вернется в рабочее состояние, мы ожидаем, что Upstream вернется в свое нормальное состояние, в котором он поддерживает пул рабочих, подключенных к внешнему серверу. Функция wait_for_queue_len () реализует цикл, который на некоторое время засыпает и проверяет, достигла ли длина очереди Upstream.Worker заданного значения.

С таким же успехом мы могли бы протестировать воркера, который блокируется до тех пор, пока внешний сервер не вернется в оперативный режим. Все, что нам нужно сделать, это запланировать звонок на Bypass.up (обход) , который должен произойти в другом процессе после некоторой задержки, и убедитесь, что вызов Upstream.perform возвращается вскоре после этого.

Прощальные слова

Надеюсь, вы нашли объяснения и примеры в этом посте полезными. Некоторые команды разработчиков уже внедряют Bypass. Например, посмотрите это сообщение в блоге Inverse о том, как они тестируют свою реализацию Facebook Graph API.

Конечно, мы рассмотрели только несколько аспектов более широкой темы тестирования HTTP API.Вот лишь несколько дополнительных вопросов, которые следует учесть:

  • Каких ответов мне следует ожидать?

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

  • Что делать, если внешняя служба слишком долго обрабатывает запрос?

  • Каковы шансы, что тестируемый HTTP API изменится в будущем? Как это повлияет на мои тесты?

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

Related Posts

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

2024 © Все права защищены.