Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Advanced 3

src/livebook/advanced3.livemd

Advanced 3

Mix.install([
  {:vega_lite, "~> 0.1.10"},
  {:kino_vega_lite, "~> 0.1.13"}
])

Forberedelse

Dette trins opave er:

  1. Åben igen index.livemd (fra https://github.com/aslakjohansen/livebook-demos) i Livebook, og evaluér den sidste celle om nødvendigt.
  2. Klik dig igennem følgende demoer:
    1. Generic servers, registries and supervision
    2. Dining Philosophers

Udgangspunkt

I denne øvelse tager vi udgangspunkt i en kodebase der modelere et bageri. Der er en række bagere, der ved hvordan man bager netop ét produkt, og der er en række kunder der altid bestiller et specifikt produkt og bliver ved med at bestille dette.

Begge roller (bager og kunde) er implementeret som genservere, og de benytter et register hver:

{:ok, baker_registry_pid} = Registry.start_link(name: BakerRegistry, keys: :duplicate)
defmodule Baker do
  use GenServer
  
  # (client) interface
  
  def start(product, name) do
    GenServer.start(__MODULE__, [product: product, name: name])
  end
  
  def order(product) do
    case Registry.lookup(BakerRegistry, product) do
      [] ->
        {:error, "No provider for '#{product}'"}
      workers ->
        {pid, _} = Enum.random(workers)
        GenServer.cast(pid, {:order, self()})
        :ok
    end
  end
  
  # callbacks
  
  @impl true
  def init([product: product, name: _] = state) do
    Registry.register(BakerRegistry, product, _value = nil)
    {:ok, state}
  end
  
  @impl true
  def handle_cast({:order, client}, [product: product, name: name] = state) do
    :timer.sleep(Enum.random(1600..3200))
    GenServer.cast(client, {:cake, product, name})
    {:noreply, state}
  end
end
{:ok, customer_registry_pid} = Registry.start_link(name: CustomerRegistry, keys: :duplicate)
defmodule Customer do
  use GenServer
  
  # (client) interface
  
  def start(product, name) do
    GenServer.start(__MODULE__, [product: product, name: "Customer #{name}"])
  end
  
  # callbacks
  
  @impl true
  def init([product: product, name: _] = state) do
    Baker.order(product)
    {:ok, state}
  end

  @impl true
  def handle_cast({:cake, product, name}, [product: p, name: n] = state) do
    IO.puts("[#{n}] Got #{product} from #{name}: Yum! Lets have one more ...")
    Baker.order(p)
    {:noreply, state}
  end
end

Lad os starte nogle bagere:

{:ok, baker_1_pid} = Baker.start("Red Velvet Cheesecake", "John")
{:ok, baker_2_pid} = Baker.start("Red Velvet Cheesecake", "Alberte")
{:ok, baker_3_pid} = Baker.start("Red Velvet Cheesecake", "Sofie")
{:ok, baker_4_pid} = Baker.start("Lemon Pie", "Freja")
{:ok, baker_5_pid} = Baker.start("Lemon Pie", "Birger")
{:ok, baker_6_pid} = Baker.start("Carrot Cake", "Hans")
{:ok, baker_7_pid} = Baker.start("Carrot Cake", "Anders")
{:ok, baker_8_pid} = Baker.start("Carrot Cake", "Signe")
bakers = [baker_1_pid, baker_2_pid, baker_3_pid, baker_4_pid, baker_5_pid, baker_6_pid, baker_7_pid, baker_8_pid]

Og nogle kunder:

products = ["Red Velvet Cheesecake", "Lemon Pie", "Carrot Cake"]

1..20
|> Enum.map(fn name -> Customer.start(Enum.random(products), name) end)

Øvelse

Gå ovenstående kode igenne, og brug mouse-over tooltips til at finde forklaringer på de elementer du ikke forstår. Der er nok en del.

Vi vil nu gerne introducere en Clerk genserver. Denne skal repræsentere en salgsassistent som kunder kommunikerer med, og som så kommunikerer med bagerne. Det ser lidt sådan her ud:

graph LR;
  Customer1-->Clerk1;
  Customer1-->Clerk2;
  Customer2-->Clerk1;
  Customer2-->Clerk2;
  Customer3-->Clerk1;
  Customer3-->Clerk2;
  Customer4-->Clerk1;
  Customer4-->Clerk2;
  Clerk1-->Baker1;
  Clerk2-->Baker1;
  Clerk1-->Baker2;
  Clerk2-->Baker2;
  Clerk1-->Baker3;
  Clerk2-->Baker3;

En kunde skal kunne spørge en salsassistent hvilke produkter der er tilgængelige. Kunden vil derefter vælge et tilfældigt produkt blandt disse og bede sangsassistenten om dette. Salgsassistenten sætter da en bager til at producere dette produkt og sender det færsige produkt til kunden. Salgsassistenterne holder styr på hvor mange af hvert produkt der er blevet solgt. Dette bør ske i en separat genserver.

Øvensen er at foretage de nødvendige ændringer for at få den nye model til at fungere. Det er dog en del ændringer. Det vil derfor være en god strategi at vælge en overskuelig delmængde af denne funktionalitet og implementere den. Derefter kan du vælge en overskuelig delmængde af den resterende funktionalitet og så videre …

Husk at tage hyppige backups.

Næste trin …

Når du er færdig finder du selv på en øvelse.