EMZETT.
Login

Kurz: Mix legt Projekte an, kompiliert, testet und verwaltet Abhängigkeiten (Hex-Pakete von hex.pm).

Teil des Kurses Elixir

Kapitel 7 von 8 im Kurs Elixir (Abschnitt „Ökosystem“). Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

Mix: das Build-Werkzeug

Mix legt Projekte an, kompiliert, testet und verwaltet Abhängigkeiten (Hex-Pakete von hex.pm).

mix new meine_app            # Projekt mit Struktur
cd meine_app
mix deps.get                 # Abhängigkeiten holen
mix compile
mix run -e 'IO.puts("hi")'
mix test                     # Tests
mix format                   # Code formatieren
iex -S mix                   # iex mit geladenem Projekt
mix help                     # alle Aufgaben

Die Struktur:

meine_app/
├── mix.exs            # Projekt, Version, Abhängigkeiten
├── lib/
│   └── meine_app.ex   # Quellcode
├── test/
│   ├── test_helper.exs
│   └── meine_app_test.exs
└── config/

Die mix.exs:

defmodule MeineApp.MixProject do
  use Mix.Project
 
  def project do
    [app: :meine_app, version: "0.1.0", elixir: "~> 1.14", deps: deps()]
  end
 
  def application, do: [extra_applications: [:logger]]
 
  defp deps do
    [
      {:jason, "~> 1.4"},
      {:req, "~> 0.4"},
      {:credo, "~> 1.7", only: [:dev, :test], runtime: false}
    ]
  end
end

Tests mit ExUnit

defmodule RechnerTest do
  use ExUnit.Case, async: true
  doctest Rechner
 
  test "Addition" do
    assert Rechner.addiere(2, 3) == 5
  end
 
  test "Division durch null" do
    assert {:error, :division_durch_null} = Rechner.teile(1, 0)
  end
 
  test "Exceptions" do
    assert_raise ArgumentError, fn -> String.to_integer("x") end
  end
 
  describe "gruss/1" do
    setup do
      %{name: "Mia"}
    end
 
    test "begrüßt", %{name: name} do
      assert Rechner.gruss(name) =~ "Mia"
    end
  end
end

Ein Highlight sind Doctests: Beispiele in der Dokumentation sind zugleich Tests.

defmodule Rechner do
  @doc """
  Addiert zwei Zahlen.
 
  ## Beispiele
 
      iex> Rechner.addiere(2, 3)
      5
  """
  def addiere(a, b), do: a + b
end

Wir können das Prinzip auch ohne Projekt ausprobieren:

ExUnit.start(autorun: false)
 
defmodule BeispielTest do
  use ExUnit.Case
 
  test "Listen" do
    assert Enum.sum([1, 2, 3]) == 6
    assert [1, 2] ++ [3] == [1, 2, 3]
  end
 
  test "Maps" do
    assert %{a: 1} = %{a: 1, b: 2}
    refute Map.has_key?(%{a: 1}, :b)
  end
end
 
ExUnit.configure(formatters: [])
ergebnis = ExUnit.run()
IO.inspect(Map.take(ergebnis, [:failures, :skipped, :total]))

Ausgabe:

%{failures: 0, skipped: 0, total: 2}

Phoenix: Web-Framework

Phoenix ist das Framework für Webanwendungen in Elixir: schnell, robust, mit eingebauten WebSockets (Channels) und LiveView für interaktive Oberflächen ohne JavaScript.

mix archive.install hex phx_new
mix phx.new blog            # Projekt anlegen
cd blog
mix ecto.create             # Datenbank anlegen
mix phx.server              # Server auf http://localhost:4000
mix phx.gen.html Inhalt Artikel artikel titel:string text:text
mix ecto.migrate

Bausteine:

TeilAufgabe
RouterURLs auf Controller/LiveViews abbilden
ControllerAnfragen verarbeiten
Templates (HEEx)HTML mit Elixir
ContextsGeschäftslogik in eigenen Modulen
EctoDatenbankschicht (Schemas, Changesets, Queries)
Channels / PubSubEchtzeit-Kommunikation
LiveViewServer rendert, Browser aktualisiert per WebSocket
# lib/blog_web/router.ex
scope "/", BlogWeb do
  pipe_through :browser
  get "/", PageController, :home
  resources "/artikel", ArtikelController
  live "/zaehler", ZaehlerLive
end
 
# lib/blog_web/live/zaehler_live.ex
defmodule BlogWeb.ZaehlerLive do
  use BlogWeb, :live_view
 
  def mount(_params, _session, socket), do: {:ok, assign(socket, :anzahl, 0)}
 
  def handle_event("plus", _, socket) do
    {:noreply, update(socket, :anzahl, &(&1 + 1))}
  end
 
  def render(assigns) do
    ~H"""
    <h1>Zähler: <%= @anzahl %></h1>
    <button phx-click="plus">+1</button>
    """
  end
end

Jeder Klick schickt ein winziges Ereignis zum Server, der nur die geänderten Teile der Seite zurücksendet. Echtzeit-Chats, Dashboards oder Spiele entstehen so ohne eigene JavaScript-Logik.

Ecto: Datenbank

defmodule Blog.Artikel do
  use Ecto.Schema
  import Ecto.Changeset
 
  schema "artikel" do
    field :titel, :string
    field :text, :string
    timestamps()
  end
 
  def changeset(artikel, attrs) do
    artikel
    |> cast(attrs, [:titel, :text])
    |> validate_required([:titel])
    |> validate_length(:titel, max: 120)
  end
end
 
import Ecto.Query
 
Repo.insert(Artikel.changeset(%Artikel{}, %{titel: "Hallo"}))
Repo.all(from a in Artikel, where: a.titel == "Hallo", order_by: [desc: a.inserted_at], limit: 5)

Weitere Einsatzgebiete

  • Nerves: Embedded-Geräte (Raspberry Pi) mit Elixir
  • Livebook: interaktive Notebooks, Datenanalyse mit Nx und Machine Learning mit Axon
  • Broadway, Oban: Datenpipelines und Hintergrundjobs
  • Hochverfügbare Systeme: Chat (Discord), Messaging, IoT, Fintech

Merke

  • mix new, mix deps.get, mix test, mix format sind die Alltagsbefehle
  • ExUnit mit assert, describe, setup; Doctests testen die Dokumentation
  • Phoenix: Router, Controller, Contexts, Ecto, Channels, LiveView
  • Hex ist die Paketquelle (hex.pm)

Übungsaufgabe

Lege mit mix new ein Projekt an, schreibe ein Modul mit Doctest und lass mix test laufen.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Prozesse, GenServer und OTP

Weiter: Referenz und Spickzettel

Alle Kapitel: Elixir im Überblick