Data Layer

This layer communicates with the GraphQL layer via a Service Context object. It is responsible for interacting with databases and object stores.

architecture

Services

In Potionx, we call modules that create, fetch, edit and delete models Services. The GraphQL layer calls these Services to respond to GraphQL requests from the web layer.

For example, here's an example of a User service similar to services generated by the model generator:

defmodule SomeProject.Users.UserService do
  alias Potionx.Context.Service
  alias SomeProject.Users.User
  alias SomeProject.Repo
  import Ecto.Query

  def count(%Service{} = ctx) do
    from(item in query(ctx), select: count(item.id))
    |> Repo.one!
  end

  def delete(%Service{} = ctx) do
    query(ctx)
    |> Repo.one
    |> case do
      nil -> {:error, "not_found"}
      entry ->
        entry
        |> Repo.delete
    end
  end

  def mutation(%Service{filters: %{id: id}} = ctx) when not is_nil(id) do
    query(ctx)
    |> Repo.one
    |> case do
      nil -> {:error, "not_found"}
      entry ->
        User.changeset(entry, ctx.changes)
        |> Repo.update
    end
  end
  def mutation(%Service{} = ctx) do
    %User{}
    |> User.changeset(ctx.changes)
    |> Repo.insert
  end

  def one(%Service{} = ctx) do
    query(ctx)
    |> Repo.one
  end

  def query(%Service{} = ctx) do
    User
    |> where(
      ^(
        ctx.filters
        |> Map.to_list
      )
    )
  end
  def query(q, _args), do: q
end

Service Context

The Service Context module (Potionx.Context.Service) represents the message format that the data layer expects to receive. The GraphQL layer converts inputs received from the web layer into Service Context messages that are then passed to the data layer.

Here's an example of a message:

%Potionx.Context.Service{
  changes: %{
    name_first: "John",
    name_last: "Smith"
  },
  files: [
    %Plug.Upload{
      content_type: "image/jpg",
      filename: "john-smith.png",
      path: "/tmp/john-smith.png"
    }
  ],
  filters: %{
    id: "AWZPR3"
  },
  roles: [:admin],
  user: %SomeProject.Users.User{
    # ...user fields
  }
}

File and Folder Naming Conventions

Potionx tries follows Phoenix's Contexts framework for naming folders. Individual model files follow Phoenix's guidelines as well. Potionx differs in how it handles naming modules responsible for manipulating models. In Potionx these are called "Services" and follows the file naming convention: "model_service.ex".

For example, files for a User model would include:

  • user_service.ex where the module is called SomeProject.Users.UserService.
  • user.ex where the module is called SomeProject.Users.User.