Criando seu próprio GenServer
Geovane Fedrecheski - geovane@lsi.usp.br
School of Engineering
University of São Paulo
Contents
- Functional Programming
- Elixir
- Hands-on
Erlang VM:
Tudo é um processo
f(g(x), h(...))
Steve Vinoski, IEEE Computing Society 2009
"... ironically, the resurgence of interest in FP languages owes a lot to Java’s popularity."
"Another reason [...] is the move toward multicore architectures."
"... developers look for the opportunity to
get more done with less."
Welcome to the Functional Web
Functional Programming Languages
Lisp
Elm
Elixir
José Valim
Core committer
Unsatisfied with thread-based concurrency
~2010
Unsatisfied with thread-based concurrency
Repetitive strain
injury
by Bruce A. Tate
So what's interesting about
this Erlang thing?
What even an "Erlang" is?
A bit of History
Erlang
context
Created in the Telecommunication industry
...
~1980
Erlang
requirements
- Concurrency
- Soft real-time
- Distribution
- Hardware Interaction
- Large Software Systems
- Complex Functionality
- Continuous Operation
- Quality Requirements
- Fault Tolerance
Programming language
Virtual Machine - BEAM
Framework - OTP
-module(fact).
-export([fac/1]).
fac(0) -> 1;
fac(N) -> N * fac(N-1).
Cases
The VM
Concurreny
Fault tolerance
Data structures
Consistent libs
Modern tooling
I loved everything that Erlang had
but I hated everything that it didn't had
- José Valim
Elixir
~2011
Elixir
Goals
-
Extensibility
-
Productivity
-
Compatibility
Elixir
Erlang
Features
Pattern Matching
> {method, path} = {:get, "temp"}
{:get, "temp"}
> method
:get
> path
"temp"
b = :binary.encode_unsigned(258)
# b has value <<1, 2>>
<<x, 2>> = b
# x has value 1
i = :binary.decode_unsigned(<<x, 2>>)
# i has value 258
request = {:get, "temp"}
case request do
{:post, path, payload} ->
send_resp 200
{:get, path} ->
send_resp 200, "{'value':17.5, 'unit': 'celsius'}"
end
defmodule Math do
def fib(0), do: 0
def fib(1), do: 1
def fib(n), do: fib(n-1) + fib(n-2)
end
Math.fib(8) # => 21
Anonymous Functions
> add = fn(a, b) -> a + b end
#Function<12.71889879/2 in :erl_eval.expr/5>
> add.(1, 2)
3
> Enum.map([17.9, 20.2, 18.4], fn(x) -> x * 2 end)
[35.8, 40.4, 36.8]
Can be passed as parameters
Processes
spawn
A lightweight thread of execution
(The key Erlang/Elixir abstraction)
How is this different from threads?
-
No shared memory
-
VM Processes
Do not share memory
Communicate via
Message Passing
message
VM Processes
Excuse me, have you ever heard of (VM) processes?
Erlang VM
Scheduler
Sched.
Sched.
Sched.
OS Processes
OS Threads
VM Processes
OS
pid = spawn(fn ->
:timer.sleep(3000)
IO.puts "hi"
end)
fn ..
spawn
self()
sleep
puts
A lightweight thread of execution
Messages
pid = spawn(...)
send(pid, "hello")
receive do
msg ->
IO.puts msg
end
fn ..
spawn
self()
receive
send
Node.spawn(
:"app@computer-2",
fn -> Hello.world end)
send
receive
app@computer-1
app@computer-2
Distributed
Same syntax
We have:
-
Isolated Processes
-
Message passing
-
Distribution
We want:
-
Fault Tolerance
dangling processes!
spawn
spawn
call the supervisor
supervisor
worker
worker
worker
worker
Supervision Tree
import Supervisor.Spec children = [ worker(Cache, []), supervisor(DatabaseSupervisor, []), supervisor(TCPSupervisor, [4040]) ] Supervisor.start_link(children, strategy: :one_for_one)
Applications
your app
http
server
logger
app@computer-1
Distributed Applications
app@computer-2
Fault Tolerance
Open Telecom Platform
-
Supervisor
-
Application
-
GenServer
-
GenEvent
OOP
Objects
Elixir
Processes
Production-ready?
CoAP
-
Constrained Application Protocol
kind of a "smaller HTTP, for IoT"
request
response
CoAP - Codes
(binary header)
0.01 (GET) (Request)
0.02 (POST) (Request)
2.05 (Content) (Response)
4.04 (Not Found) (Response)
000.00001
000.00010
010.00101
100.00100
CoAP
parser
1
0
0
0.01
123
01000000 00000001 0000000001111011 11111111
255
64 1 0 123 255
(empty)
(empty)
(empty)
%Message(type: :con,
code: :get,
msg_id: 123,
options: [])
int coap_parseHeader(coap_header_t *hdr,
const uint8_t *buf,
size_t buflen)
{
if (buflen < 4)
return COAP_ERR_HEADER_TOO_SHORT;
hdr->ver = (buf[0] & 0xC0) >> 6;
if (hdr->ver != 1)
return COAP_ERR_VERSION_NOT_1;
hdr->t = (buf[0] & 0x30) >> 4;
hdr->tkl = buf[0] & 0x0F;
hdr->code = buf[1];
hdr->id[0] = buf[2];
hdr->id[1] = buf[3];
return 0;
}
https://github.com/1248/microcoap/blob/master/coap.c
C
def decode(<<1::2, type::2, token_len::4,
code_class::3, code_detail::5,
msg_id::16,
token::binary-size(token_len),
rest::binary>>) do
def decode(<<1::2, type::2, token_len::4,
code_class::3, code_detail::5,
msg_id::16,
token::binary-size(token_len),
rest::binary>>) do
code = decode_code(code_class, code_detail)
token = decode_token(token, token_len)
{options, payload} =
decode_options_and_payload(rest)
%Message{
version: 1,
type: Registry.from(:types, type),
code: code,
msg_id: msg_id, token: token,
options: options, payload: payload
}
end
int coap_parseHeader(coap_header_t *hdr,
const uint8_t *buf,
size_t buflen)
{
if (buflen < 4)
return COAP_ERR_HEADER_TOO_SHORT;
hdr->ver = (buf[0] & 0xC0) >> 6;
if (hdr->ver != 1)
return COAP_ERR_VERSION_NOT_1;
hdr->t = (buf[0] & 0x30) >> 4;
hdr->tkl = buf[0] & 0x0F;
hdr->code = buf[1];
hdr->id[0] = buf[2];
hdr->id[1] = buf[3];
return 0;
}
Hands on!
- Can read & parse its own Service Description
- Can Register itself in the Broker
- Has a REST API
- Enforces Access Control
- Has unit tests
- Can keep state
Goal:
Build a
Temperature Sensor
service that:
#1
Create a new project
$ mix new temperature_sensor --sup
$ cd temperature_sensor
$ mix test
$ iex -S mix
#2
- put jsonld file at "priv" dir
def read_service_description do
Path.join("#{:code.priv_dir(:temperature_sensor)}",
"service_description.jsonld")
|> File.read!()
end
$ mix test
$ iex -S mix
#3
# mix.exs
defp deps do
[{:poison, "~> 3.0"}]
end
def read_service_description do
Path.join("#{:code.priv_dir(:temperature_sensor)}",
"service_description.jsonld")
|> File.read!()
|> Poison.decode!()
end
$ mix deps.get
$ mix test
#4
def register(sd) do
Task.start_link(fn ->
BrokerHTTPClient.register(sd)
end)
end
# mix.exs
defp deps do
[
{:poison, "~> 3.0"},
{:broker_http_client,
git: "git@git.febrace.org.br:swarm-unit/broker-http-client-ex.git"}
]
end
$ mix deps.get
$ mix test
#5
Plug.Adapters.Cowboy.child_spec(:http,
TemperatureSensor.Router,
[], [port: sd_port])
# mix.exs
defp deps do
[
{:cowboy, "~> 1.0.0"},
{:plug, "~> 1.0"},
{:poison, "~> 3.0"},
{:broker_http_client,
git: "git@git.febrace.org.br:swarm-unit/broker-http-client-ex.git"}
]
end
$ mix deps.get
$ iex -S mix
#6
defmodule RouterTest
$ mix test
#7
match _ do
Logger.warn "Invalid request at #{conn.request_path}"
send_resp(conn, 404, "EEEPA")
end
$ mix test
$ iex -S mix
#8
plug BrokerHTTPClient, :authorize
$ iex -S mix
$ curl \
-H "x-m2m-origin: alice"
http://localhost:8001/temperature-sensor/value
- Can read & parse its own Service Description
- Can Register itself in the Broker
- Has a REST API
- Enforces Access Control
- Has unit tests
- Can keep state
Goal:
Build a
Temperature Sensor
service that:
Obrigado
Gracias
Merci
Thanks
Appendix
:observer.start
(slide intentionally left blank)
(this is the appendix!)
Criando seu próprio GenServer
By Geovane Fedrecheski
Criando seu próprio GenServer
- 425