Advanced 3
Mix.install([
{:vega_lite, "~> 0.1.10"},
{:kino_vega_lite, "~> 0.1.13"}
])
Forberedelse
Dette trins opave er:
-
Åben igen
index.livemd
(fra https://github.com/aslakjohansen/livebook-demos) i Livebook, og evaluér den sidste celle om nødvendigt. -
Klik dig igennem følgende demoer:
- Generic servers, registries and supervision
- 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.