programming erlang ch14 1 - andstudy/forge GitHub Wiki

ProgrammingErlang/14장 OTP개론

Open Telecom Platform

  • OTP는 애플리케이션 운영 체제이며 대규모의 무정지 분산 애플리케이션을 만드는 데 사용하는 라이브러리와 프로시저들의 집합이다.
  • OTP 비헤이비어 - 문제의 비기능적인 부분을 해결함

제네릭 서버로 가는 길

(!) 문제의 비기능적인 부분 VS 기능적인 부분

서버1 기본 서버

모든 서버의 기본 패턴

    -module(server1).
    -export([start/2, rpc/2]).
    
    start(Name, Mod) ->
        register(Name, spawn(fun() -> loop(Name, Mod, Mod:init()) end)).
    
    rpc(Name, Request) ->
        Name ! {self(), Request},
        receive
            {Name, Response} -> Response
        end.
    
    loop(Name, Mod, State) ->
        receive
    	{From, Request} ->
                {Response, State1} = Mod:handle(Request, State),
                From ! {Name, Response},
                loop(Name, Mod, State1)
        end.

    -module(name_server).
    -export([init/0, add/2, whereis/1, handle/2]).
    -import(server1, [rpc/2]).
    
    %% client routines
    add(Name, Place) -> rpc(name_server, {add, Name, Place}).
    whereis(Name)    -> rpc(name_server, {whereis, Name}).
    
    %% callback routines
    init() -> dict:new().
    
    handle({add, Name, Place}, Dict) -> {ok, dict:store(Name, Place, Dict)};
    handle({whereis, Name}, Dict)    -> {dict:find(Name, Dict), Dict}.

    server1:start(name_server, name_server).
    name_server:add(blah, "blahblah").
    name_server:whereis(blah).

서버2 트랜잭션이 있는 서버

    -module(server2).
    -export([start/2, rpc/2]).
    
    start(Name, Mod) ->
        register(Name, spawn(fun() -> loop(Name,Mod,Mod:init()) end)).
    
    rpc(Name, Request) ->
        Name ! {self(), Request},
        receive
            {Name, crash} -> exit(rpc);
            {Name, ok, Response} -> Response
        end.
    
    loop(Name, Mod, OldState) ->
        receive
    	{From, Request} ->
    	    try Mod:handle(Request, OldState) of
    		{Response, NewState} ->
    		    From ! {Name, ok, Response},
    		    loop(Name, Mod, NewState)
    	    catch
    		_:Why ->
    		    log_the_error(Name, Request, Why),
    		    %% send a message to cause the client to crash
    		    From ! {Name, crash},
    		    %% loop with the *original* state
    		    loop(Name, Mod, OldState)
    	    end
        end.
    
    log_the_error(Name, Request, Why) ->
        io:format("Server ~p request ~p ~n"
    	      "caused exception ~p~n", 
    	      [Name, Request, Why]).

서버3 핫 코드 교체가 되는 서버

    -module(server3).
    -export([start/2, rpc/2, swap_code/2]).
    
    start(Name, Mod) ->
        register(Name, 
    	     spawn(fun() -> loop(Name,Mod,Mod:init()) end)).
    
    swap_code(Name, Mod) -> rpc(Name, {swap_code, Mod}).
    
    rpc(Name, Request) ->
        Name ! {self(), Request},
        receive
            {Name, Response} -> Response
        end.
    
    loop(Name, Mod, OldState) ->
        receive
    	{From, {swap_code, NewCallBackMod}} ->
    	    From ! {Name, ack},
    	    loop(Name, NewCallBackMod, OldState);
    	{From, Request} ->
    	    {Response, NewState} = Mod:handle(Request, OldState),
    	    From ! {Name, Response},
    	    loop(Name, Mod, NewState)
        end.

    -module(name_server1).
    -export([init/0, add/2, whereis/1, handle/2]).
    -import(server3, [rpc/2]).
    
    %% client routines
    add(Name, Place) -> rpc(name_server, {add, Name, Place}).
    whereis(Name)    -> rpc(name_server, {whereis, Name}).
    
    %% callback routines
    init() -> dict:new().
    
    handle({add, Name, Place}, Dict) -> {ok, dict:store(Name, Place, Dict)};
    handle({whereis, Name}, Dict)    -> {dict:find(Name, Dict), Dict}.

    -module(new_name_server).
    -export([init/0, add/2, all_names/0, delete/1, whereis/1, handle/2]).
    -import(server3, [rpc/2]).
    
    %% interface
    all_names()      -> rpc(name_server, allNames).
    add(Name, Place) -> rpc(name_server, {add, Name, Place}).
    delete(Name)     -> rpc(name_server, {delete, Name}).
    whereis(Name)    -> rpc(name_server, {whereis, Name}).
    
    %% callback routines
    init() -> dict:new().
        
    handle({add, Name, Place}, Dict) -> {ok, dict:store(Name, Place, Dict)};
    handle(allNames, Dict)           -> {dict:fetch_keys(Dict), Dict};
    handle({delete, Name}, Dict)     -> {ok, dict:erase(Name, Dict)};
    handle({whereis, Name}, Dict)    -> {dict:find(Name, Dict), Dict}.

서버4 트랜잭션과 핫 코드 교체

    -module(server4).
    -export([start/2, rpc/2, swap_code/2]).
    
    start(Name, Mod) ->
        register(Name, spawn(fun() -> loop(Name,Mod,Mod:init()) end)).
    
    swap_code(Name, Mod) -> rpc(Name, {swap_code, Mod}).
    
    rpc(Name, Request) ->
        Name ! {self(), Request},
        receive
            {Name, crash} -> exit(rpc);
            {Name, ok, Response} -> Response
        end.
    
    loop(Name, Mod, OldState) ->
        receive
    	{From, {swap_code, NewCallbackMod}} ->
    	    From ! {Name, ok, ack},
    	    loop(Name, NewCallbackMod, OldState);
    	{From, Request} ->
    	    try Mod:handle(Request, OldState) of
    		{Response, NewState} ->
    		    From ! {Name, ok, Response},
    		    loop(Name, Mod, NewState)
    	    catch
    		_: Why ->
    		    log_the_error(Name, Request, Why),
    		    From ! {Name, crash},
    		    loop(Name, Mod, OldState)
    	    end
        end.
    
    log_the_error(Name, Request, Why) ->
        io:format("Server ~p request ~p ~n"
    	      "caused exception ~p~n", 
    	      [Name, Request, Why]).

서버5 좀 더 재미있게

    -module(server5).
    -export([start/0, rpc/2]).
    
    start() -> spawn(fun() -> wait() end).
    
    wait() ->
        receive
    	{become, F} -> F()
        end.
    
    rpc(Pid, Q) ->
        Pid ! {self(), Q},
        receive
    	{Pid, Reply} -> Reply
        end.

    -module(my_fac_server).
    -export([loop/0]).
    
    loop() ->
        receive
    	{From, {fac, N}} ->
    	    From ! {self(), fac(N)},
    	    loop();
    	{become, Something} ->
    	    Something()
        end.
        
    fac(0) -> 1;
    fac(N) -> N * fac(N-1).

gen_server 시작하기

  1. 콜백 모듈 이름 정하기

  2. 인터페이스 루틴 작성하기

  3. 콜백 루틴 작성하기

     -module().
     %% gen_server_mini_template
     
     -behaviour(gen_server).
     -export([start_link/0]).
     %% gen_server callbacks
     -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
     	 terminate/2, code_change/3]).
     
     start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
     
     init([]) -> {ok, State}.
     
     handle_call(_Request, _From, State) -> {reply, Reply, State}.
     handle_cast(_Msg, State) -> {noreply, State}.
     handle_info(_Info, State) -> {noreply, State}.
     terminate(_Reason, _State) -> ok.
     code_change(_OldVsn, State, Extra) -> {ok, State}.
    
     -module(my_bank).
     
     -behaviour(gen_server).
     -export([start/0]).
     %% gen_server callbacks
     -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
     	 terminate/2, code_change/3]).
     -compile(export_all).
     
     
     start() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
     stop()  -> gen_server:call(?MODULE, stop).
     
     new_account(Who)      -> gen_server:call(?MODULE, {new, Who}).
     deposit(Who, Amount)  -> gen_server:call(?MODULE, {add, Who, Amount}).
     withdraw(Who, Amount) -> gen_server:call(?MODULE, {remove, Who, Amount}).
     
     
     
     init([]) -> {ok, ets:new(?MODULE,[])}.
     
     handle_call({new,Who}, _From, Tab) ->
         Reply = case ets:lookup(Tab, Who) of
     		[]  -> ets:insert(Tab, {Who,0}), 
     		       {welcome, Who};
     		[_] -> {Who, you_already_are_a_customer}
     	    end,
         {reply, Reply, Tab};
     handle_call({add,Who,X}, _From, Tab) ->
         Reply = case ets:lookup(Tab, Who) of
     		[]  -> not_a_customer;
     		[{Who,Balance}] ->
     		    NewBalance = Balance + X,
     		    ets:insert(Tab, {Who, NewBalance}),
     		    {thanks, Who, your_balance_is,  NewBalance}	
     	    end,
         {reply, Reply, Tab};
     handle_call({remove,Who, X}, _From, Tab) ->
         Reply = case ets:lookup(Tab, Who) of
     		[]  -> not_a_customer;
     		[{Who,Balance}] when X =< Balance ->
     		    NewBalance = Balance - X,
     		    ets:insert(Tab, {Who, NewBalance}),
     		    {thanks, Who, your_balance_is,  NewBalance};	
     		[{Who,Balance}] ->
     		    {sorry,Who,you_only_have,Balance,in_the_bank}
     	    end,
         {reply, Reply, Tab};
     handle_call(stop, _From, Tab) ->
         {stop, normal, stopped, Tab}.
     
     handle_cast(_Msg, State) -> {noreply, State}.
     handle_info(_Info, State) -> {noreply, State}.
     terminate(_Reason, _State) -> ok.
     code_change(_OldVsn, State, Extra) -> {ok, State}.
    
⚠️ **GitHub.com Fallback** ⚠️