글로 다이어그램을 그려요 – PlantUML

소프트웨어 설계를 하다 보면 UML 다이어그램으로 표현해야 할 일이 종종 있다. 객체지향 설계이거나, 여러 가지 모듈이 연동되어 동작하는 시스템이거나 또는 상태 변화를 설명해야 할 때 UML 다이어그램은 아주 좋은 표현 방법이다.

다이어그램은 그림이다. 컴퓨터를 이용해 그림을 그리려면 그림판과 같은 도구가 필요하다. (물론 그림판으로 다이어그램을 그리는 장인은 게으른 개발자 중엔 없으리라 생각한다) 당연하게도 오래전부터 UML 다이어그램을 그리기 위한 많은 도구가 개발되어 사용되고 있다. 역사와 전통을 가진 IBM의 Rational 시리즈, Boland의 Together Architect, MS의 Visio 그리고 국산 UML 도구의 대명사 Plastic을 오픈소스화한 StarUML 등이 대표적이라 할 수 있다. UML 도구는 아니지만, PowerPoint나 Keynote 같은 프레젠테이션 도구를 이용하는 ppt 장인들도 있다. 모두 강력한 도구임은 분명하다. 그런데 모두 GUI를 가진 도구이다. 마우스를 이용하기 위해 키보드에서 손이 떨어지면 생산성도 떨어질 것 같아 자괴감이 드는 개발자들에게는 굉장히 귀찮은 도구가 아닐 수 없다.

 

키보드만으로 다이어그램을 그릴 수는 없을까?

이 화려한 그래픽을 보라!

MUD 게임의 이 화려한 그래픽을 보라!

우리는 이미 오래전 키보드만으로 진행하는 텍스트 기반의 온라인 RPG 게임인 MUD를 경험했다. 커맨드를 입력하고, 뇌내(腦內) 그래픽카드를 풀가동해 머릿속에 화려한 맵을 그리며 게임을 했던 바로 그 경험이다. 연식 인증인가? 그 경험처럼 텍스트만으로 다이어그램을 표현해보자.

 

Alice -> Bob: Authentication Request
Alice <- Bob: Authentication Response

 

plantuml_logo예제는 Alice가 Bob에게 인증을 요청하고 Bob이 요청에 대한 응답을 주는 절차를 표현한 간단한 문장이다. 이름과 화살표, 그리고 동작을 직관적으로 설명하고 있다. 그대로 다이어그램으로 바꿀 수 있으면 좋을 것 같다. 그리고 그런 일이 실제로 가능하다. 오픈소스 UML 도구인 PlantUML을 이용하면 된다.

PlantUML을 이용해 예제를 시퀀스 다이어그램으로 변환하면 다음 그림과 같다.

auth

Awesome!! GUI 도구를 이용해 같은 다이어그램을 그리려면 인스턴스 둘을 끌어다 놓고, 라벨링을 두 번 하고, 화살표를 두 번 연결하고, 또 라벨링을 두 번 해야 한다. 우리의 손은 마우스와 키보드를 넘나들며 바쁜 시간을 보낼 수밖에 없다. 게다가 칼각을 원하는 강박증 환자라면 어긋난 1 ~ 2 픽셀에 스트레스를 받으며 저주받은 손을 원망하고 있을 것이 뻔하다.

 

 

설치 방법

PlantUML은 Java로 구현되어 있으므로 JVM만 돌아갈 수 있으면 어떠한 머신에서도 동작한다. 가장 쉬운 설치방법은 웹사이트의 다운로드 페이지에서 미리 컴파일된 jar 파일을 받아 사용하는 것이다. 이클립스나 IntelliJ를 이용한다면 플러그인으로 설치할 수도 있다. 역시 웹사이트를 참조하자.

키보드에서 손을 떼기 싫어서 PlantUML을 사용하려는데, 웹사이트에서 내려받기 위해 마우스를 잡아야 한다는 것이 모순으로 느껴질 수도 있다. 그런 키보드 성애자분들에게는 cli를 이용해 설치하는 방법을 추천한다.

  • Devian 계열 Linux
$ sudo apt-get install plantuml
  • Redhat 계열 Linux
$ sudo yum install plantuml
  • macOS
$ brew install plantuml

 

 

사용 방법

시퀀스 다이어그램을 표현했던 앞의 예제를 PlantUML 문법으로 표현하면 다음과 같다.

@startuml 
Alice -> Bob: Authentication Request 
Alice <- Bob: Authentication Response 
@enduml

그대로 txt 파일로 저장하고 plantuml을 실행하면 위와 같은 다이어그램 이미지 파일이 생성된다.

$ plantuml filename.txt

 

클래스 다이어그램은 다음과 같이 표현할 수 있다.

@startuml
class Class01 {
  String data
  void methods()
}
Class01 <|-- Class02
@enduml

거의 코드 수준이다. 개발자에게 이보다 더 좋은 문법이 있을 수 없다. 역시 txt 파일로 저장하고 plantuml을 실행하면 다음과 같이 클래스 다이어그램이 생성된다.

class

 

그렇다면 스테이트 다이어그램은 어떨까?

@startuml
[*] --> Running: Start
Running --> Pause: Pause
Pause --> Running: Resume
Running --> [*]: Stop
@enduml

state

시퀀스 다이어그램의 문법과 거의 흡사한 문법으로 스테이트 다이어그램을 얻을 수 있다.

이 밖에도 유즈케이스, 액티비티, 컴포넌트 다이어그램 등 UML 다이어그램 대부분을 간단하고 직관적인 문법으로 표현할 수 있다. 다이어그램에 따라 문법이 조금씩 다르지만, 전체적으로 볼 때 직관적이다 하는 그런 기운이 느껴진다. 자세한 문법은 역시 웹사이트를 참조하자. 한글 매뉴얼을 제공하고 있어 쉽게 학습할 수 있다.

 

약을 한번 팔아보자.

다이어그램을 그려야 하는데 다음과 같은 문제가 있어 어려운 분에게 이 약 PlantUML을 강력하게 추천한다.

  • 키보드에서 손을 떼면 생산성이 떨어진다고 생각하는 키보드 성애자
  • 마우스를 사용하면 생산성이 떨어진다고 생각하는 마우스 혐오자
  • 1 ~ 2 픽셀이 어긋나 있는 것을 두고 볼 수 없는 라인 강박증 환자
  • 회사에서 고가의 UML 도구를 사주지 않아 파워포인트로 한 땀 한 땀 그리고 있는 PPT 장인
  • 마우스 때문에 손목이 아픈 손목 터널 증후군 환자
  • bash와 vi가 없으면 아무것도 할 수 없는 리눅스 덕후
  • MUD 게임의 향수에 젖어 있는 아재 개발자

 

PlantUML로 글 쓰듯이 다이어그램을 그려보자.

 

 

밑바닥 개발자의 레이어 등정기

나는 밑바닥 개발자였다.

적어도 2년 전까진 그랬다. 여기서 밑바닥이라는 것은 계급론적 최하층을 뜻하는 것은 아니다. 일반적으로 표현하는 소프트웨어 레이어의 가장 아래, 로우엔드, 하드웨어 디펜던시한 레이어를 뜻한다. 다시 말해 나는 디바이스 드라이버 개발자였다.

android_layer

주 업무는 전자 부품들의 데이터 시트를 참조하여 원하는 동작을 할 수 있도록 펌웨어 / 드라이버를 개발하는 일이었다. 특별한 IDE도 없이 텍스트 에디터와 gcc를 이용해 개발을 했고, 콘솔 로그를 참조하여 디버깅을 했다. 그나마 일반적인 로직이라면 gdb 또는 콘솔 로그로 디버깅할 수 있었지만, 논리적 결함이 아닌 전자적 설정 오류나 타이밍 이슈는 오실로스코프를 이용해야 했다. 실제 데이터가 어떤 전기적 신호로 흘러가고 있는지 확인해야 했기 때문이다.

1324016978_39

전자공학이 아닌 전산학을 전공한 나는 오실로스코프 사용법은 커녕 회로도 하나 읽지 못하는 상태였다. 당연히 적응에 상당한 시간이 걸릴 수밖에 없었다. 계속해서 좀 더 로지컬한 일을 원했지만 받아들여지지 않았다. 하지만 나는 호모 디지피엔스였다. 어느새 익숙해져 실력을 인정을 받으며 10년이라는 세월이 흘렀다.

 

 

신분 레이어 상승의 욕구

실력을 인정받는다는 것은 달콤한 일이다. 그 달콤함을 버리기란 쉽지 않다. 그러나 여전히 가슴 한쪽에는 상위 레이어로 올라가고 싶은 마음이 자라고 있었다. 지금은 비록 인정받고 있다고 하지만 결국 나는 우물 안 개구리일 것이 분명했다.

limp-bizkit-501cdee5305f0

Hey~ Drop the bit! 내가 가진 건 고작 0과 1, start and stop, on and off, 그게 전부지만 사실은 greater still, 그것이 바로 남자의 길~

쿵작쿵작 쿵짜작쿵짝 흐르는 010101101 비트의 세계가 남자의 길이라지만, 좀 더 많은 소프트웨어 기술들이 배틀을 하는 윗동네의 공기를 느끼고 싶었다.

그래서 과감히 회사를 그만두었다. 확실한 대책은 없었다. 일단 쉬면서 생각해 보기로 했다. 대책 없는 자유는 한없이 지루했고, 유유히 흘러가는 강물처럼 하루하루가 지나갔다. 어느새 나는 주부가 되었다.

어느덧 시간이 흐르고, 나는 제조업이 아닌 웹서비스 회사로 이직했다. 딱히 프론트엔드 같은 최상위 레이어를 원한 것은 아니었지만, 조금은 극적인 레이어 상승을 할 수 있을 것 같았다. 하지만 처음 내게 주어진 업무는 여전히 프로토콜 설계와 제어 모듈, 데몬 개발이었다. 그나마 더는 비트 단위의 프로토콜이 아닌 JSON 문서로 사람과 기계가 모두 이해할 수 있는 시멘틱을 전달할 수 있어 한 걸음 앞으로 나아간 기분은 들었다. 게다가 사람과 기계가 모두 이해할 수 있는 시멘틱은 석사 논문의 주제였던 시멘틱 웹의 궁극적인 목표가 아니었던가!

 

 

그리고 1년 후

드디어 서버 개발자로 변신했다. 물론 여전히 애플리케이션 레이어보다는 커널 레이어에 가까운 플랫폼 API 서버를 개발하고 있지만 말이다. 사실 디바이스 드라이버를 이용하는 소프트웨어에 API를 제공하는 것이나, 플랫폼을 이용하는 소프트웨어에 API를 제공하는 것이나 크게 다를 것은 없다. 그러나 그것을 구현하기 위해서 많은 것들을 새로 또는 다시 공부해야 했다.

2016년 한해 로우엔드에서 백엔드로 이동하며 공부했던 것들을 열거해보면 다음과 같다.

 

Python

logo_python주 종목이었던 c를 버렸다. 필요하다면 c로도 플랫폼 API 서버를 만들 수는 있다. 그렇지만 훨씬 쉬운 방법이 있는데 적합하지 않은 언어를 굳이 고집할 필요는 없다. 그래서 python과 flask를 공부했다.
커널의 기능을 이용하는 간단한 운영 도구도 python으로 만들 수 있다. 게다가 c로 된 라이브러리도 쉽게 사용할 수 있어 기존에 만들었던 라이브러리도 사용할 수 있다. python으로 넘어온 것은 좋은 선택이었던 것 같다.

 

RESTful API

restful_api지난 10년의 경험은 javascript ui 개발을 지원하기 위한 웹 브라우저 포팅을 제외하면 웹과 거의 관련이 없었다. HTTP에 대한 지식은 겨우 학부 수준이었다. GET과 SET 메소드를 이용해 데이터를 주고받는 정도에 그쳤다. 오래전에 배운 내용은 이미 많이 변화했다. 인터페이스를 만들기 위해 HTTP와 RESTful API에 관해서도 공부해야 했다.

 

DB와 ORM

db_orm역시 학부 이후로 DB를 거의 다루지 않았다. 제로보드 기반의 커뮤니티, 텍스트큐브와 워드프레스 기반의 블로그 등을 운영하면서 필요에 따라 조금씩 사용했던 select from where가 전부다. PK, FK, 테이블간의 관계 표현 등, 가물가물한 기억을 더듬으며 다시 공부해야 했다.
그러나 직접 쿼리를 만들 필요는 없었다. 모델링을 하고 SQLAlchemy같은 ORM을 이용하여 모델을 정의해두면 나머지는 ORM이 알아서 해준다. 신기한 세상이다.

 

로드 밸런싱 및 배포 도구

dist_tools대규모 웹 서비스를 개발해야 하니 규모를 생각하지 않을 수 없다. 여러 대의 서버를 구축하여 로드를 분산하고, 소프트웨어를 모든 서버에 자동으로 배포해야 하는 등의 개발 이외의 일들이 산재해있었다. 임베디드의 세계에서는 전혀 알 수 없었던 부분이다. CI 서버에서 빌드 되어 나온 펌웨어를 OTA 서버에 배포하면 끝이었다. 실제 단말로의 배포는 브로드캐스팅으로 진행되지만, 거기까진 신경 쓰지 않아도 되는 영역이었다.

 

open source

opensource-logo-300x259필요한 것이 있으면 일단 만들기 시작했던 구시대 개발자의 습관도 버렸다. 필요한 것은 이미 다 있다. 찾아서 사용하자. know-how보다는 know-where가 중요하다. 그러고도 개발자라면 오픈소스를 쓰자!

 

 

다시 2년전

이직을 위한 최종 면접에서 면접관 한 분이 이렇게 물었다.

임베디드 10년 경력이면 더 깊게 파는 것이 더 도움이 되지 않을까요?

물론 더 깊게 공부해 전문가(expert)가 되는 것도 좋은 선택이다. 언젠가는 전문가가 될 수도 있지만, 현실은 그렇지 않다. 전문가를 가장한 꼰대가 될 확률이 훨씬 높다. 항상 해왔던 일만 반복하는 부품이 될 확률도 만만치 않다.

나는 대답했다.

한 분야를 깊이 알기 위해서는 일단 넓게 알아야 한다고 생각합니다. 다양하게 체험해보지 않으면 깊이를 가늠하기 어렵습니다. 깊이 알기 위해 더 많은 분야를 체험해 보고 싶습니다.

그 대답의 결과로 다양한 분야를 체험하며 2016년을 보냈다. 그리고 그 체험은 앞으로도 당분간은 계속 이어질 것 같다.

 


 

이 글은 이상한모임 대림절 이벤트에 기고하기 위해 작성되었습니다. 대림절이란 블라블라블라… 자세한 내용은 링크를 참조하세요 🙂

 

타 언어에서 rust library 사용하기

 

일반적으로 시스템 라이브러리라함은 네이티브 언어로 작성되었음을 의미한다. 그리고 보통 네이티브 언어는 c/c++과 같은 언어를 의미한다. 시스템 라이브러리는 다른 언어로 작성한 프로그램에서도 이용할 수 있어야 한다. 그래서 많은 프로그래밍 언어들이 c/c++로 작성된 라이브러리를 사용하기 위한 패키지(이하 ffi 패키지)를 제공한다. java의 jni (java native interface), python의 ctype, ruby와 javascript의 ffi (foreign function interface) 등이 바로 그 것이다. 물론 rust에서도 c 라이브러리를 사용하기 위해 ffi 패키지를 제공한다.

rust는 c/c++과 마찬가지로 (다른 용도로도 사용할 수 있지만 기본적으로는) 시스템 프로그래밍을 위한 언어다. 따라서 rust로 작성된 라이브러리도 다른 언어에서 사용할 수 있어야 한다. 이를 위해 rust는 c 라이브러리와 같은 구조의 동적 라이브러리를 만들 수 있는 옵션을 제공한다.

 

 

동적 라이브러리 만들기

rust는 cargo라는 패키지 매니저가 많은 것들을 대신 해주기 때문에 c 라이브러리를 만드는 것과는 비교도 할 수 없을 정도로 편하다. c로 소스 코드를 작성하고 소스 코드만큼 많은 양의 makefile을 작성하던 과거의 나는 이제 안녕!

…은 개뿔. 프로그램이 커지고, 그래서 모듈이 많아지고, 따라서 디펜던시가 증가하고, 그로 인해 옵션이 많이지면 cargo도 복잡하긴 매한가지다. 너무 좋아하진 말자.

언어를 배운다는 것은 Hello, world! 를 출력하는 방법을 배우는 것이다. 그러니까 Hello, world! 를 출력하는 rust 라이브러리를 만들어 보자. 싫다면 다른 라이브러리를 알아서 만들어 보길 추천한다. 예를 들어 알파고알파고 라거나 알파고 같은 것 말이다.

일단 cargo를 이용해 새 패키지를 생성한다.

$ cargo new hello
$ cd hello
$ tree
.
├── Cargo.toml
└── src
    └── lib.rs

우리의 요구사항은 Hello, world! 를 출력하는 것이다. 요구 사항을 만족하는 소스 코드를 lib.rs 파일에 입력한다.

fn hello() {
    println!("Hello, world!");
}
$ cargo build
    Compiling hello v0.1.0 (file:///Users/arzhna/dev/hello)
src/lib.rs:1:1: 3:2 warning: function is never used: `hello`, #[warn(dead_code)] on by default
src/lib.rs:1 fn hello() {
src/lib.rs:2     println!("Hello, world!");
src/lib.rs:3 }  

그대로 빌드하니 dead_code 어쩌고 하는 무시무시한 경고 메시지를 출력하며 컴파일이 됐다. 경고 메시지는 뭔지 잘 모르겠으니 무시하고, 라이브러리가 잘 만들어졌나 확인해보았다.

$ ls target/debug/lib*
target/debug/libhello.rlib

이럴 수가! 확장자가 rlib 이다. rlib은 rust 정적 라이브러리 파일의 확장자다. ffi 패키지들은 so 또는 dylib 이라는 확장자를 가진 동적 라이브러리를 지원한다. python에서 제공하는 ctype의 c 라이브러리 로드 클래스는 이름부터 cdll(c dynamic link library)이다. rlib은 못 쓴다는 뜻이다.

 

살려야 한다! 동적 라이브러리를 만들어야 한다!

 

동적 라이브러리를 만들기 위해서는 동적 라이브러리를 만들기 위한 옵션을 설정해야 한다. Cargo.toml 파일을 열고 적당한 위치에 아래와 같이 이 패키지는 동적 라이브러리여야만 해!라고 명시하기 위한 [lib] 섹션을 추가하자.

[lib]
crate-type = ["dylib"]

그리고 다시 빌드해보자.

$ cargo build
    Compiling hello v0.1.0 (file:///Users/arzhna/dev/hello)
src/lib.rs:1:1: 3:2 warning: function is never used: `hello`, #[warn(dead_code)] on by default
src/lib.rs:1 fn hello() {
src/lib.rs:2     println!("Hello, world!");
src/lib.rs:3 }
$   
$ ls target/debug/lib*
target/debug/libhello.dylib

(역시 경고는 무시하고) 확인해보니 이번에는 dylib 이라는 확장자를 가진 동적 라이브러리가 잘 생성되었다. 만세!

 

그렇다면 이대로 쓰면 될까? 경고가 찝찝한데?

 

메시지를 다시 살펴보니 function is never used 라고 경고하고 있다. hello() 함수를 어디서도 사용하고 있지 않고 있기 때문이다. 아무도 사용하지 않는 dead_code 가 있는 경우 경고 메시지를 발생시키는 것이 rust 컴파일러의 기본 설정이다.

라이브러리가 제공하는 API 함수라면 라이브러리 내에서 사용하고 있지 않아도 전혀 이상할 것이 없는데 왜 경고 메시지가 나올까? 혹시 API 함수가 아니라면? 그렇다. 처음에 작성했던 hello() 함수는 로컬 함수였다. 외부에서 사용할 수 있도록 선언되지 않았기 때문에 경고를 발생시킨 것이다. 로컬 함수를 API 함수로 바꾸기 위해서는 몇가지 어노테이션을 추가해야 한다.

 

pub extern fn hello() {
    println!("Hello, world!");
}

pubextern 이라는 어노테이션이 추가되었다. pub은 public 함수임을 선언하기 위해, extern은 외부에서도 사용할 수 있음을 선언하기 위해 붙이는 어노테이션이다.

 

이제 외부에서 사용할 수 있는 동적 라이브러리를 만들 수 있을까? 아직이다. 대부분의 컴파일러는 컴파일 과정에서 네임 맹글링(name mangling)이라는 작업을 수행한다. 정의되어 있는 함수명을 정해진 규칙에 의해 변경하는 작업이다. 그런데 불행히도 언어마다 맹글링 규칙이 다르다. c와 c++을 혼용해 사용하는 프로그램에서는 extern "C" {...}와 같은 코드들을 흔하게 볼 수 있다. 이는 c와 c++의 맹글링 규칙이 다르기 때문에 c 스타일로 맞춰주기 위한 코드다.

c 라이브러리를 로드하는 패키지들은 c 스타일의 맹글링만을 고려한다. 따라서 rust 컴파일러가 c 스타일의 맹글링을 하도록 해야한다. 다음과 같이 #[no_mangle] 어노테이션을 추가해 rust 컴파일러의 맹글링 스위치를 꺼버리자.

#[no_mangle]
pub extern fn hello() {
    println!("Hello, world!");
}

이제 다른 언어에서 사용할 수 있는 동적 라이브러리를 생성할 준비가 끝났다. 빌드하자.

 

$ cargo build
   Compiling hello v0.1.0 (file:///Users/arzhna/dev/test/hello)
$ ls target/debug/lib*          
target/debug/libhello.dylib

경고 메시지도 없고 동적 라이브러리도 제대로 생성되었다.

신난다!

easy

 

 

rust 동적 라이브러리 사용하기

서두에서 언급했던 바와 같이 대부분의 언어들은 c/c++ 라이브러리를 사용하기 위한 패키지를 제공한다. 이전 섹션에서 만든 rust 라이브러리는 c 스타일로 컴파일된 동적 라이브러리다. 따라서 c/c++ 라이브러리를 사용하기 위한 패키지를 그대로 이용할 수 있다.

python

파이썬은 ctype이라는 패키지를 제공한다. ctype의 cdll이라는 클래스를 통해 c/c++ 라이브러리를 로드할 수 있다. 백문이 불여일견! 바로 테스트해보자.

# hello.py

from ctypes import cdll

# libhello 라이브러리를 로드한다.
lib = cdll.LoadLibrary("./target/release/libhello.dylib")

# libhello 라이브러리가 제공하는 hello() api를 호출한다.
lib.hello()

print("done!")

실행하면?

$ python hello.py
Hello, world!
done!
$

굿잡!

 

c

c에서 rust 라이브러리를 사용하기 위해서는 헤더 파일이 필요하다. rust 라이브러리가 제공하는 API들의 프로토타입을 c 스타일로 선언해주면 오케이!

// hello.h

void hello(void);

 

헤더 파일이 생겼으니 이제 소스 코드를 작성하고, c 라이브러리 링크하던 대로 옵션 주고, 그렇게 하던 대로 빌드하면 된다.

// hello.c

#include <stdio.h>
#include "hello.h"

int main(){
    hello();
    return 0;
}
$ gcc -lhello -L./target/release/ -I. hello.c -o hello
$ ./hello 
Hello, world!

성공!

 

swift
swift는 objective-c를 기반으로 만들어져 있다. 당연히 c와 호환성이 우수하다. rust로 작성된 라이브러리지만 c 라이브러리와 다를바 없으므로 Bridging-Header만 만들면 그대로 사용할 수 있다.

 

java
자바는 jni를 만들면되는데… 귀찮으니 패스하자.

 

ruby
안타깝게도 나는 ruby 언어를 모른다. ffi 패키지가 있다카더라.

 

js
역시 ffi를 이용하면 된다던데?

 

go


불행히도 그나마 할 줄 아는 언어가 c, python, java 밖에 없다. 그러니 예제는 여기서 끝!