# ExOperation

[![Build Status](](

A library for making domain operations wrapped in a single database transaction.

**WARNING:** This library is in active development state. Breaking changes are possible between minor releases.

## Resources

* [Documentation](
* [Hex package](
* [ExOperation: organizing business logic with operations in Elixir](
* [Talk about ExOperation at Elixir-Lang Moscow meetup]( (in Russian)
* [Example Phoenix app](

## Example

An operation definition:

defmodule MyApp.Book.Update do
  use ExOperation, params: %{
    id!: :integer,
    title!: :string,
    author_id: :integer

  def validate_params(changeset) do
    |> Ecto.Changeset.validate_length(:title, min: 5)

  def call(operation) do
    |> find(:book, schema: MyApp.Book, preload: [:author])
    |> find(:author, schema: MyApp.Author, id_path: [:author_id], optional: true)
    |> step(:result, &do_update(operation.context, &1, operation.params))
    |> after_commit(&send_notifcation(&1))

  defp do_update(context, txn, params) do
    |> Ecto.Changeset.cast(params, [:title])
    |> Ecto.Changeset.put_assoc(:author,
    |> Ecto.Changeset.put_assoc(:updated_by, context.current_user)
    |> MyApp.Repo.update()

  defp send_notification(txn) do
    # …
    {:ok, txn}

The call:

context = %{current_user: current_user}

with {:ok, %{result: book}} <- MyApp.Book.Update |>, params) do
  # …

### Usage with Phoenix

An example Phoenix app can be found here: [ex_operation_phoenix_example](

## Features

* [Railway oriented]( domain logic pipeline.
* Running all steps in a single database transaction. It uses [Ecto.Multi]( inside.
* Params casting & validation with `Ecto.Changeset`. Thanks to [params]( library.
* Convenient fetching of entitites from the database.
* Context passing. Useful for passing current user, current locale etc.
* Composable operations: one operation can call another through `suboperation/3` function.
* Changing operation scenario based on previous steps results with `defer/2`.
* Hooks: `before_transaction/1`, `after_commit/1`.

## Installation

Add the following to your `mix.exs` and then run `mix deps.get`:

def deps do
    {:ex_operation, "~> 0.5.0"}

Add to your `config/config.exs`:

config :ex_operation,
  repo: MyApp.Repo

where `MyApp.Repo` is the name of your Ecto.Repo module.