Phoenix Core Advanced: Contexts, Plugs & Telemetry

Avatar

By squashlabs, Last Updated: June 21, 2023

Phoenix Core Advanced: Contexts, Plugs & Telemetry

Phoenix is a useful web framework built on top of the Elixir programming language. It provides a solid foundation for building scalable and fault-tolerant web applications. While Phoenix offers a lot of out-of-the-box functionality, there are some advanced features that can further enhance your development experience and allow you to build more robust applications. In this article, we will explore three such advanced features in Phoenix Core: Contexts, Plugs, and Telemetry.

Utilizing Advanced Plug Constructs in Phoenix Pipelines

Plugs are a fundamental concept in Phoenix that enable you to compose and manipulate requests and responses in a declarative and composable manner. While the basic plug constructs are useful, Phoenix also provides some advanced plug constructs that allow for more fine-grained control over the request/response flow. Let's take a look at two advanced plug constructs: :halt and :pass.

The :halt plug allows you to stop the execution of the plug pipeline and return a response immediately. This can be useful in scenarios where you want to perform some authorization or validation checks and return an error response if the checks fail. Here's an example:

defmodule MyApp.AuthorizationPlug do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    if unauthorized?(conn) do
      conn
      |> put_status(:unauthorized)
      |> put_resp_header("content-type", "application/json")
      |> send_resp(:unauthorized, %{error: "Unauthorized"})
      |> halt()
    else
      conn
    end
  end

  defp unauthorized?(conn) do
    # Check if the request is authorized
    # Return true if unauthorized, false otherwise
  end
end

In this example, the MyApp.AuthorizationPlug plug checks if the request is unauthorized. If it is, it halts the pipeline and returns an unauthorized response. Otherwise, it continues with the pipeline execution.

The :pass plug, on the other hand, allows you to skip the current plug and continue with the next one in the pipeline. This can be useful in scenarios where you want to conditionally skip certain plugs based on some request parameters. Here's an example:

defmodule MyApp.DebugModePlug do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    if debug_mode?(conn) do
      conn
    else
      conn
      |> pass()
    end
  end

  defp debug_mode?(conn) do
    # Check if the request is in debug mode
    # Return true if in debug mode, false otherwise
  end
end

In this example, the MyApp.DebugModePlug plug checks if the request is in debug mode. If it is, it continues with the pipeline execution. Otherwise, it skips the current plug and continues with the next one.

Related Article: Phoenix Design Patterns: Actor Model, Repositories, and Events

Understanding Phoenix Contexts for Domain-Driven Design

Domain-Driven Design (DDD) is an architectural approach that emphasizes modeling software based on the core business domains. Phoenix Contexts provide a way to organize and encapsulate related functionality within a domain in a structured manner. They enable you to define clear boundaries between different parts of your application and promote separation of concerns.

A Phoenix Context is a module that groups together related functionality, such as models, schemas, queries, and commands, within a specific domain. It provides a high-level API for interacting with that domain, hiding the underlying implementation details. Let's take a look at an example:

defmodule MyApp.Accounts do
  alias MyApp.Repo
  alias MyApp.Accounts.User

  def create_user(attrs) do
    %User{}
    |> User.changeset(attrs)
    |> Repo.insert()
  end

  def get_user(id) do
    Repo.get(User, id)
  end
end

In this example, the MyApp.Accounts context encapsulates the functionality related to user accounts. It provides two functions, create_user/1 and get_user/1, which abstract away the details of creating and retrieving user records from the database.

Implementing Custom Telemetry Events in Phoenix

Telemetry is a useful tool for gathering and analyzing metrics in your Phoenix applications. It allows you to collect data about various events that occur during the execution of your code and use that data to gain insights into the performance and behavior of your application. Phoenix provides a built-in Telemetry system that you can leverage to implement custom telemetry events and metrics.

To implement custom telemetry events in Phoenix, you need to define a new event and instrument it in your code. Here's an example:

defmodule MyApp.Web.Controllers.UserController do
  use MyApp.Web, :controller

  def create(conn, params) do
    Telemetry.instrument([:my_app, :controller, :create_user], %{params: params}, fn ->
      # Code to create a user
    end)
  end
end

In this example, the MyApp.Web.Controllers.UserController module defines a create/2 function that creates a user. We use the Telemetry.instrument/3 function to instrument the code block that creates the user. The first argument to Telemetry.instrument/3 is a list representing the event name, and the second argument is a map containing any additional metadata you want to associate with the event.

Once you have instrumented your code, you can subscribe to the custom telemetry event and process the data. Here's an example:

defmodule MyApp.TelemetryHandler do
  def handle_event([:my_app, :controller, :create_user], measurements, metadata, _config) do
    # Process the telemetry event data
  end
end

In this example, the MyApp.TelemetryHandler module defines a handle_event/4 function that handles the [:my_app, :controller, :create_user] event. The measurements argument contains the measured data, the metadata argument contains the additional metadata associated with the event, and the _config argument contains the configuration for the Telemetry system.

Tracking Metrics in Phoenix: Examples and Best Practices

Tracking metrics is an essential aspect of monitoring and optimizing the performance of your Phoenix applications. By measuring various aspects of your application's performance, you can identify bottlenecks, pinpoint areas for improvement, and ensure that your application is meeting its performance goals. In this section, we will explore some examples and best practices for tracking metrics in Phoenix.

One common metric to track is the response time of your web requests. By measuring the time it takes for a request to be processed and returned to the client, you can identify slow or inefficient parts of your application. Here's an example of how you can track the response time using the :timer module:

defmodule MyApp.Web.Plugs.ResponseTimeTracker do
  import Plug.Conn
  require Logger

  def init(opts), do: opts

  def call(conn, _opts) do
    start_time = :timer.tc(:microseconds)

    conn
    |> put_resp_header("x-request-start", to_string(start_time))
    |> before_send(fn(conn) ->
      response_time = :timer.tc(:microseconds, start_time)
      Logger.info("Response time: #{response_time} microseconds")
      conn
    end)
  end
end

In this example, the MyApp.Web.Plugs.ResponseTimeTracker plug tracks the response time of a request. It uses the :timer.tc/2 function to measure the time taken from the start of the request to the end of the request. The measured response time is then logged using the Logger.info/1 function.

Another metric you may want to track is the number of requests being processed by your application. By monitoring the request rate, you can identify periods of high traffic and scale your application accordingly. Here's an example of how you can track the request rate using the :telemetry module:

defmodule MyApp.Telemetry.Handler do
  def handle_event([:phoenix, :endpoint, :start], measurements, _metadata, _config) do
    TelemetryMetrics.measure("request_rate", 1)
    {:ok, measurements}
  end
end

In this example, the MyApp.Telemetry.Handler module handles the [:phoenix, :endpoint, :start] event, which is triggered when a request starts processing. It uses the TelemetryMetrics.measure/2 function to increment the request_rate metric by 1.

Related Article: Internationalization & Encoding in Elixir Phoenix

Additional Resources



- Elixir - Official Website

- Phoenix - Official Website

- How Plug works in Phoenix? - Phoenix Framework

You May Also Like

Optimizing Database Queries with Elixir & Phoenix

This tutorial delves into the importance of database interactions and application loads in Elixir. Learn how Ecto optimizes database interactions, ex… read more

Exploring Phoenix: Umbrella Project Structures,Ecto & More

The article "Exploring Phoenix: Umbrella Project Structures, Ecto & More" is a deep dive into best practices for umbrella project structures, Liv… read more

Building Real-Time Apps with Phoenix Channels & WebSockets

Building real-time applications can be a complex task, but with Phoenix Channels and WebSockets in Elixir, it becomes much easier. This article explo… read more

Elixir’s Phoenix Security: Token Auth & CSRF Prevention

Ensure your web applications are secure with this in-depth look at Phoenix security features. Learn about token-based authentication, CSRF prevention… read more

Integrating Phoenix Web Apps with Payment, Voice & Text

Integrate Phoenix web apps with payment platforms and communication functionalities using Elixir. Learn how to set up voice and SMS functionalities, … read more

Deployment Strategies and Scaling for Phoenix Apps

Shipping, scaling, and monitoring Phoenix applications can be a complex task. This article provides proven techniques and best practices for deployin… read more

Implementing Enterprise Features with Phoenix & Elixir

Implementing Enterprise Features with Phoenix & Elixir offers insights into integrating Single Sign-On, LDAP authentication, and audit trails in … read more

Phoenix with Bootstrap, Elasticsearch & Databases

Enhance your Phoenix applications by integrating with Bootstrap, Elasticsearch, and various databases. From configuring Phoenix with Ecto for MySQL, … read more