Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/8thlight/ex_state
Database-backed state machines and statecharts for Elixir
https://github.com/8thlight/ex_state
elixir
Last synced: about 1 month ago
JSON representation
Database-backed state machines and statecharts for Elixir
- Host: GitHub
- URL: https://github.com/8thlight/ex_state
- Owner: 8thlight
- License: mit
- Created: 2020-03-11T16:25:05.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2020-05-27T16:12:51.000Z (about 4 years ago)
- Last Synced: 2024-05-19T09:05:00.097Z (about 2 months ago)
- Topics: elixir
- Language: Elixir
- Homepage:
- Size: 128 KB
- Stars: 110
- Watchers: 10
- Forks: 3
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Lists
- awesome-stars - 8thlight/ex_state - Database-backed state machines and statecharts for Elixir (Elixir)
README
# ExState
[![Hex.pm](https://img.shields.io/hexpm/v/ex_state_ecto.svg)](https://hex.pm/packages/ex_state_ecto)
[![Hex Docs](https://img.shields.io/badge/hexdocs-release-blue.svg)](https://hexdocs.pm/ex_state_ecto/ExState.html)Elixir state machines, statecharts, and workflows for Ecto models.
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `ex_state` to your list of dependencies in `mix.exs`:```elixir
def deps do
[
{:ex_state_ecto, "~> 0.3"}
]
end
```Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/ex_state](https://hexdocs.pm/ex_state).## Usage
### Without Ecto
- [Example](test/ex_state/examples/vending_machine_test.exs)
### Ecto Setup
```elixir
defmodule MyApp.Repo.Migrations.AddWorkflows do
def up do
# Ensure Ecto.UUID support is enabled:
execute("CREATE EXTENSION IF NOT EXISTS pgcrypto")ExState.Ecto.Migration.up()
enddef down do
end
end
``````elixir
config :ex_state, repo: MyApp.Repo
```### Defining States
Define the workflow:
```elixir
defmodule SaleWorkflow do
use ExState.Definitionalias MyApp.Repo
workflow "sale" do
subject :sale, Saleparticipant :seller
participant :buyerinitial_state :pending
state :pending do
on :send, :sent
on :cancel, :cancelled
endstate :sent do
parallel do
step :acknowledge_receipt, participant: :buyer
step :close, participant: :seller
endon :cancelled, :cancelled
on_completed :acknowledge_receipt, :receipt_acknowledged
on_completed :close, :closed
endstate :receipt_acknowledged do
step :close, participant: :seller
on_completed :close, :closed
endstate :closed
state :cancelled do
on_entry :update_cancelled_at
end
enddef guard_transition(:pending, :sent, %{sale: %{address: nil}}) do
{:error, "missing address"}
enddef guard_transition(_from, _to, _context), do: :ok
def update_cancelled_at(%{sale: sale}) do
sale
|> Sale.changeset(%{cancelled_at: DateTime.utc_now()})
|> Repo.update()
end
end
```Add the workflow association to the subject:
```elixir
defmodule Sale do
use Ecto.Schema
use ExState.Ecto.Subjectimport Ecto.Changeset
schema "sales" do
has_workflow SaleWorkflow
field :product_id, :string
field :cancelled_at, :utc_datetime
end
end
```Add a `workflow_id` column to the subject table:
```
alter table(:sales) do
add :workflow_id, references(:workflows, type: :uuid)
end
```### Transitioning States
Using `ExState.transition/3`:
```elixir
def create_sale(params) do
Multi.new()
|> Multi.insert(:sale, Sale.new(params))
|> ExState.Ecto.Multi.create(:sale)
|> Repo.transaction()
enddef cancel_sale(id, user_id: user_id) do
sale = Repo.get(Sale, id)ExState.transition(sale, :cancel, user_id: user_id)
end
```Using `ExState.Execution.transition_maybe/2`:
```elixir
sale
|> ExState.create()
|> ExState.Execution.transition_maybe(:send)
|> ExState.persist()
```Using `ExState.Execution.transition/2`:
```elixir
{:ok, execution} =
sale
|> ExState.load()
|> ExState.Execution.transition(:cancelled)ExState.persist(execution)
```Using `ExState.Execution.transition!/2`:
```elixir
sale
|> ExState.load()
|> ExState.Execution.transition!(:cancelled)
|> ExState.persist()
```### Completing Steps
```elixir
def acknowledge_receipt(id, user_id: user_id) do
sale = Repo.get(Sale, id)ExState.complete(sale, :acknowledge_receipt, user_id: user_id)
end
```### Running Tests
Setup test database
```bash
MIX_ENV=test mix ecto.create
mix test
```## TODO
- Extract `ex_state_core`, and other backend / db packages.
- Multiple workflows per subject.
- Allow configurable primary key / UUID type for usage across different
databases.
- Tracking event history with metadata.
- Add SCXML support
- Define schema for serialization / json API usage / client consumption.
- [Parallel states](https://xstate.js.org/docs/guides/parallel.html#parallel-state-nodes)
- [History states](https://xstate.js.org/docs/guides/history.html#history-state-configuration)