Languages/Erlang2009.06.23 14:07
Erlang과 Java를 연동시키려면 Erlang의 runtime을 띄울 때 epmd가 구동되도록 해야 합니다. 별도로 실행 방법을 알 필요는 없고, erl을 띄울 때 node 이름과 host name을 지정해 주면 됩니다.

$> erl -name foo@foo.bar

그렇게 하면 epmd가 자동으로 듭니다. 그래야 나중에 Java가 붙을 때 Java 프로그램의 node 이름이 해당 epmd에 등록 되기 때문에, 반드시 이렇게 해 주어야 합니다.

Erlang과 Java 프로그램의 연동은 서로 메시지를 주고 받는 과정을 통해서 이루어집니다. 따라서 연동을 하려면 Java 프로그램과 연동할 Erlang 프로세스가 떠 있어야 합니다. 다음의 간단한 프로그램 예제를 봅시다. 이 파일을 ets_java_frontend.erl로 저장하고 컴파일 해 둡니다.

-module(ets_java_frontend).
-export([start/0, shutdown/0, show/0, rpc/1]).

start() ->
    LoopPid = spawn(fun loop/0),
    register(ets_server, LoopPid).

shutdown() ->
    Pid = whereis(ets_server),
    Pid ! quit.

show() ->
    Pid = whereis(ets_server),
    Pid ! show.

rpc(T) ->
    Pid = whereis(ets_server),
    Pid ! T.

loop() ->
    EtsHandle = ets:new(test, [set]),
    loop(EtsHandle).

loop(Handle) ->
    receive
        show ->
            List = ets:tab2list(Handle),
            io:format("~p~n", [List]),
            loop(Handle);
        quit ->
            {ok, terminated};
        {tuple, T} ->
            io:format("msg is received ~p~n", [T]),
            ets:insert(Handle, T),
            loop(Handle);
        true ->
            io:format("unprocessable msg is received~n"),
            loop(Handle)
    end.

이 Erlang 프로그램은 {tuple, T} 형태의 메시지를 받으면 T (투플입니다)를 ets에 저장하는 프로그램입니다. spawn해서 프로세스를 생성할 때 해당 프로세스의 Pid를 register() 호출을 통해 등록하고 있는데, Java쪽에서 이 Erlang 프로그램에 메시지를 보낼 때 register()할 때 전달한 프로세스 이름(위의 경우에는 ets_server)을 통해 해당 프로세스의 mbox에 메시지를 전달하므로, register()를 반드시 호출해 프로세스 이름을 등록해 두어야 합니다.

이렇게 하면 Java 프로그램 안에서 Erlang 프로세스에 메시지를 전달할 수 있는데요. 공짜로 되는 것은 아니고 (당연하겠죠?) Erlang 패키지에 포함되어 있는 JInterface라는 라이브러리를 통하여야 합니다. Windows라면 해당 라이브러리는 C:/Program Files/erl5.7.2/lib/jinterface-1.5.1/priv에 있고, Unix라면 아마 /usr/lib/erlang/ 아래 어딘가에 해당 jar가 있을 겁니다. ^^;

잡소리는 집어치우고 해당 프로그램 예제를 간단하게 훑어보면...

import java.io.IOException;
import com.ericsson.otp.erlang.*;

public class ETP {

    private String node;
    private OtpNode self;
    private OtpMbox mbox;

    /**
     * create ETP instance.
     * @param name name of the erlang server node.
     * @throws IOException
     */
    public ETP(String node) throws IOException {
       
        this.node = node;
       
        try {
            this.self = new OtpNode("java_erl_bjlee");
        } catch ( IOException e ) {
            System.err.println("cannot create ETP instance.");
            e.printStackTrace();
            throw e;
        }

        this.mbox = self.createMbox("etp_java_client");
    }

    private OtpErlangTuple createMessage(OtpErlangTuple tuple) {
       OtpErlangObject[] aTerm = new OtpErlangObject[2];
       aTerm[0] = new OtpErlangAtom("tuple");
       aTerm[1] = tuple;
       OtpErlangTuple msg = new OtpErlangTuple( aTerm );
       return msg;
   }

   private void send(OtpErlangTuple msg) {
       mbox.send("ets_server", this.node, msg);
   }
   
    public boolean put(String key, String value) {
        OtpErlangObject[] aTuple = new OtpErlangObject[2];
        aTuple[0] = new OtpErlangAtom(key);
        aTuple[1] = new OtpErlangAtom(value);
        OtpErlangTuple tuple = new OtpErlangTuple(aTuple);
       
        OtpErlangTuple msg = createMessage(tuple);
       
        send(msg);
       
        return true;
    }
}

this.node에는 통신할 Erlang 노드의 이름이 들어갑니다. 이 페이지 맨 위에서처럼 했다면 foo가 들어가야겠군요. 먼저 OtpNode를 생성하구요. (이름을 주어야 합니다. Java 프로그램은 별개의 Erlang 노드인 것처럼 동작하거든요.) 그 다음에 message 송수신에 사용될 mbox를 만듭니다.

그 다음에는 이제 메시지를 보내 Erlang 프로그램이 원하는 작업을 하도록 만들면 됩니다. put 메소드의 코드를 보시면 되겠습니다. mbox.send() 함수가 최종적으로 호출되는데, 이 때 첫 번째 인자로는 통신할 erlang 프로세스의 registered name이 들어가고, 두 번째 인자로는 해당 프로세스가 돌고 있는 Erlang node, 그리고 세 번째 인자로는 실제로 전송할 메시지가 들어갑니다.

이제 Main 클래스를 다음과 같이 작성하고 돌려보면...

public class Main {

    /**
     * @param args
     */
    public static void main(String[] args) {
       
        ETP etp;
       
        try {
            etp = new ETP("foo");
        } catch (IOException e) {
           
            e.printStackTrace();
            return;
        }
       
        etp.put("test1", "value1");
        etp.put("test2", "value2");
       
        System.out.println("finished");
    }
}

참. 돌리기 전에 erl 셸에서 ets_java_frontend:start()를 먼저 실행해주어야 합니다. ㅎㅎ 어쨌든 실행하고 위의 Java 프로그램을 돌려보면... erl 쪽 화면에 메시지들이 찍히면서 ets에 내가 전송한 투플들이 저장됩니다. 저장이 잘 되었는지는 erl 셸에서 ets_java_frontend:show()를 실행하여 확인할 수 있습니다.

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 땅꼬마

    감사합니다, 좋은 정보 얻어가네요 ^^

    2009.12.24 10:28 신고 [ ADDR : EDIT/ DEL : REPLY ]

Extremely Agile/UML2007.10.29 11:01
I. 컴포넌트와 클래스 라이브러리

컴포넌트가 인터페이스를 갖도록 다이어그램을 그리는 것은 간단합니다만, 그것을 실제로 구현하는 것은 조금 어렵습니다. 컴포넌트는 잘 구현하면 그야말로 컴포넌트가 되지만, 제대로 구현하지 못하면 컴포넌트인지 클래스 라이브러리인지 알수가 없는 지경이 되거든요.

짐작하시겠습니다만 컴포넌트와 클래스 라이브러리는 많이 다릅니다. 클래스 라이브러리는 그 라이브러리를 쓰는 쪽에서 deploy의 방법을 결정하는 반면, 컴포넌트는 이미 고정되어 있는 상태라는 것이죠. 클래스 라이브러리의 경우에는 쓰는 쪽에서 어떻게 쓰느냐에 따라서 그 인스턴스들이 어떻게 만들어지고 이용되는지가 굉장히 달라질 가능성이 있습니다만, 컴포넌트의 경우에는 그 컴포넌트 단위로 배치가 이루어지기 때문에 컴포넌트 안에 속한 객체들이 만들어지고 이용되는 과정이 정형적입니다. 그리고 대부분의 경우, 컴포넌트 내부에서 무슨 일이 벌어지고 있는지는 클라이언트 코드 쪽에서는 전혀 알 필요가 없지요.


II. 인터페이스의 구현

그런데 어떤 코드 집합이 컴포넌트가 될 것인지 아니면 클래스 라이브러리가 될 것인지를 결정짓는 가장 핵심적인 요소는 무엇일까요? 제 생각에, 그것은 그 인터페이스를 어떻게 구현할 것이냐, 하는 문제인 것 같습니다. 다음과 같이 설계된 컴포넌트가 있다고 칩시다.

 component.JPG

이 컴포넌트가 외부에 제공해야 하는 인터페이스는 Request Admission Interface 하나뿐입니다. (앞선 글에서도 언급했었습니다만, 이 인터페이스는 반드시 하나의 Java Interface로 구현될 필요는 없습니다. 필요하다면 여러 개의 인터페이스로 구성될 수 있지요.) 예제 구성을 편하게 하기 위해, 우선은 이 인터페이스가 단 하나의 Java Interface 'RequestAdmissionInterface'로 구현된다고 치겠습니다. 그리고 이 인터페이스를  RequestrAdmissionInterfaceImpl 클래스로 구현했다고 해 보죠. 이렇게 구현된 인터페이스를 클라이언트 코드 안에서 다음과 같이 사용해야 한다면 어떨까요?

RquestAdmissionInterface o = new RequestAdmissionInterfaceImpl(); 
o.foo(); 

클라이언트 코드 안에서 RequesutAdmissionInterface의 구현에 사용된 클래스의 이름에 대해서 알아야 하는데, 그렇게 되면 클라이언트 코드 안에 불필요한 종속성이 발생하게 됩니다. 이런 종류의 종속성은 클래스 라이브러리를 사용할 때에는 흔히 발생하는 일입니다. (List 인터페이스를 구현하는 Concrete List 클래스들을 사용하는 경우들을 생각해보시기 바랍니다.) 위의 코드는 RequestAdmissionInterfaceImpl 이외의 다른 Implementation 클래스를 사용해야 하는 경우 반드시 변경되어야 합니다. (물론 new 를 해 주는 부분의 코드만 변경되면 충분하겠지만 말입니다.) 그러니, 저린 식의 사용방법을 강제하는 컴포넌트는 '그다지 컴포넌트 스럽지 않은' 컴포넌트라고 할 수도 있겠습니다. 컴포넌트를 사용하는 사람은 컴포넌트의 내부구조가 어떻게 변경되느냐에 무관하게 클라이언트 코드를 작성할 수 있어야 하기 때문이죠.

그렇다면 '단순히 인터페이스 클래스들을 만든 다음 그 인터페이스들을 구현하는 것 만으로는' 컴포넌트가 가져야 하는 요건을 충족하는 코드를 작성할 수 없겠군요. 그렇다면 이 문제를 해결하기 위해 참고할만한 기존의 사례는 없나요?

있습니다. RMI쪽이 그 대표적인 사례이죠. RMI는 외부에서 참조할 인터페이스를 구현하는 클래스에 의해 생성된 객체를 모종의 Repository에 등록하게 한 다음, 필요한 순간에 그 객체에 대한 레퍼런스를 가져와서 쓰도록 강제합니다. 이런 구현 방법의 장점은 '아무도 어떤 인터페이스의 실제 구현에 대해서는 알 필요가 없다'는 것이고, 단점은 '해당 인터페이스를 쓰려면 언제나 중앙의 repository를 한 번은 거쳐야 한다'는 것입니다. 물론 이 단점은 단점이라고 보기에는 좀 사소하죠. repository 개념 덕분에, RMI는 그 단순성을 네트워크-wide 하게 확장해 나갈 수 있었으니까요.

그렇다면, 우리도 RMI와 비슷하게 인터페이스 repository 객체를 만든 다음에 나중에 특정한 컴포넌트의 인터페이스가 필요할 때는 거기서 레퍼런스를 가져와서 쓰도록 하면 될 것 같군요. 어차피 컴포넌트는 deploy가 되는 순간에 그 컴포넌트를 대표하는 객체가 생성되어 있다고 봐도 좋을테니, 레퍼런스를 가져오는 것에 큰 문제는 없어 보입니다.

물론 중앙에 repository를 두는 것이 맘에 안 들 수도 있습니다. 그럴 경우에는 어떻게 해야 하나요? 컴포넌트의 외피를 적절한 클래스에 의해 모델링 한 다음, 그 클래스에 인터페이스 객체의 레퍼런스를 가져오기 위한 메소드를 구현하는 것을 생각할 수 있습니다.  

class DecisionSupportComponent {
   ...
   RequestAdmissionInterface getRequestAdmissionInterface() {
      ... 
   }
   ...
}

이렇게 하면 DecisionSupportComponent 객체를 사용하는 쪽에서는 인터페이스의 구현이 어떤 클래스에 의해 만들어지는지는 알 수가 없겠습니다. 그 코드는 getRequestAdmissionInterface() 함수 안에만 있을 테니까요. 모든 컴포넌트들이 이런 표준적인 메커니즘에 의해 인터페이스 레퍼런스를 제공하도록 만들고 싶은 경우도 있을텐데, 그렇게 할 경우 모든 인터페이스 클래스들이 또 다른 인터페이스 클래스를 상위 클래스로 가져야 한다는 문제가 있어 솔루션이 지나치게 복잡해 질 수 있습니다. 그러니 그건 그냥 패스하도록 하죠.


이 글은 스프링노트에서 작성되었습니다.

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Extremely Agile/UML2007.10.27 00:19

I. UML 컴포넌트

UML 컴포넌트 다이어그램을 구성하는 하나의 컴포넌트는 컴포넌트와 거기에 붙은 여러개의 인터페이스로 그릴 수 있습니다. 인터페이스는 provided interface와 required interface의 두 종류로 나뉘는데, 전자는 그 컴포넌트가 제공하는 인터페이스이고, 후자는 다른 컴포넌트가 제공하는 인터페이스를 사용한다는 의미입니다. 그러니까 어떤 컴포넌트 입장에서 봤을 때 제일 중요한 인터페이스는 provided 인터페이스인 셈입니다. 컴포넌트 다이어그램을 그리는 사람 입장에서도 그렇습니다. 그 다이어그램을 그리는 사람은 어떤 인터페이스가 어떤 컴포넌트에 속해야 하는지가 가장 뚜렷이 드러나도록 다이어그램을 그리려고 할 테니 말이죠.

사용자 삽입 이미지
 

이렇게 달랑 컴포넌트 하나만 놓고 설명하는 건 좀 심심하니까, 이번에는 컴포넌트가 여러개 등장하는 컴포넌트 다이어그램을 보도록 하겠습니다. 보시다시피 컴포넌트의 인터페이스 간에는 의존성 관계가 있습니다. 그 방향은 언제나 일정하죠. provided interface 쪽으로 화살표가 가게 되어 있습니다. (뭐 당연한 일이겠죠?)

컴포넌트 다이어그램

컴포넌트 다이어그램 - 누르면 커집니다



II. UML 컴포넌트의 인터페이스

그런에 위의 그림을 잘 보면, 인터페이스의 이름이 마치 자바 클래스나 자바 인터페이스의 이름과 비슷하게 만들어져 있다는 것을 보실 수 있을 겁니다. 그렇다면 저 동그라미 하나는 하나의 자바 인터페이스에 대응되어야 하나요?

흔히 컴포넌트 인터페이스를 정의할 때, 그 인터페이스가 가지는 역할을 중심으로 인터페이스 이름을 결정합니다. 해당 인터페이스를 호출하면 해당 컴포넌트가 어떤 종류의 역할을 수행하게 되는지를 중심으로 인터페이스 이름을 결정한다, 뭐 그렇게 생각해도 되겠죠.

그런데 문제는 그런 식으로 정해진 인터페이스가 꼭 자바 인터페이스 하나에 대응되는 것은 아닐 수도 있다는 것이죠. 자바의 인터페이스/클래스를 어떻게 나눌 것이냐 하는 문제는 단순히 '역할'이라는 하나의 관점만 사용해서는 풀기가 좀 힘듭니다. "UML, 실전에서는 이것만 쓴다"라는 책의 클래스 다이어그램 부분을 읽어보면 짐작할 수 있는 사항입니다만,  클래스들을 나눌 때에는 "의존도를 최소화해야"하고 "변경을 국지화해야 한다"는 등의 원칙들이 강하게 작용하거든요. 그러니 위의 그림에서 볼 수 있는 RequestAdmissionInterface 같은 것은 사실 그 인터페이스를 사용하는 외부 컴포넌트가 두 개 이상일 경우에는 역시 두 개 이상의 자바 인터페이스에 의해 모델링 될 수가 있습니다. 그렇다면 이렇게 정리하면 되겠군요.

하나의 컴포넌트 인터페이스는 하나 이상의 자바 인터페이스에 대응될 수 있다 



이 말이 어쩐지 좀 이상하게 들린다는 분들은 Enterprise Architect같은 UML 툴을 한 번 써 보시기 바랍니다. 실제로 저 동그라미 하나에 하나 이상의 자바 인터페이스 (혹은 C++ Abstract 클래스) 에 대한 '링크'를 만들어 넣을 수가 있습니다.

이런 관점은 애자일 적으로 봐도 유효합니다. 컴포넌트 다이어그램을 그리는 단계에서는 컴포넌트 안에 무슨 클래스들이 만들어질지 모르는 경우가 허다한데, 인터페이스 클래스의 이름까지 고정적으로 붙여버린다는 것은 솔직히 너무한 일 아니겠어요?

그렇게 본다면 위의 컴포넌트 다이어그램에서 RequestAdmissionInterface같은 이름은 어쩐지 이상하군요. 꼭 자바 인터페이스 같은 뉘앙스를 풍기니 말이죠. 그냥 Request Admission Interface같이 띄어쓰기를 해 주는 편이 더 낫겠어요.


참고문헌

"UML, 실전에서는 이것만 쓴다" (인사이트)

이 글은 스프링노트에서 작성되었습니다.


신고
Posted by 이병준

소중한 의견, 감사합니다. ^^