대표적인 선형 자료구조가 StackQueue라면, 대표적인 비선형 자료구조로는 TreeGraph가 있다고 이야기할 수 있다. 그래프 이론에서는 tree도 graph에 속하고(회로가 없는 연결 무향 그래프를 tree라고 하기 때문) 자료구조에서도 tree가 그래프의 일종이라고 보는 듯 하다.

'나무'라는 의미에 맞게, 뿌리와 가지, 잎 개념이 들어가 있는 계층적 구조를 띄고 있다. 주된 목적은 탐색이며, 따라서 검색 엔진, 데이터베이스, 라우터 알고리즘 등 계층적 데이터를 다루는 수많은 곳에서 응용되고 있다. 아래는 tree의 예다.

현실에서 볼 수 있는 나무와는 다르게 위에서 아래 방향으로 뻗어나오는 모양이다. 따라서 root(뿌리)도 맨 위에 있다. 트리는 기본적으로 NodeEdge로 구성된다. A, B, C처럼 각각의 요소를 node라고 부르고, node들을 이어주는 간선을 edge라고 부른다. Node는 종류에 따라 아래처럼 나뉜다.

  • Root node : tree의 시작점이 되는 노드. 위 그림에서는 A가 root node이며, tree에서 root node는 최대 하나다.
  • Parent node : tree의 부분집합sub-tree라고 쳤을 때(위 그림에서는 D, H, I로 이뤄진 tree), sub-tree에서의 root node를 의미한다. Root node만 유일하게 parent node가 없다.
  • Child node : Parent node에서 뻗어나온 노드를 의미한다. Parent node와 child node는 상대적인 개념이라, 위 그림에서 C는 A의 child node이면서 F와 G의 parent node이다.
  • Leaf node : tree의 끝점이 되는 노드(Child node가 하나도 없는 노드). 단말(terminal) 노드라고도 부른다.
  • Internal node : Leaf node가 아닌 노드를 의미한다.

Tree에는 해석학의 이론이 존재하기에, 해석에 관련된 몇가지 개념이 들어가 있다. 그들 중 가장 기본적인 게 차수, 높이, 레벨이다.

  • 차수(degree) : 어떤 노드가 가지고 있는 child node의 수를 의미한다. 위 그림에서는 leaf node들을 제외하면 E만 차수가 1이다. leaf node는 child node가 없는 노드를 의미하므로 일반적으로 leaf node에 대해서는 차수를 이야기하지 않는다. Tree에서 발견되는 가장 높은 차수를 tree의 차수라고 말하기도 한다. binary tree, ternary tree 등 n-ary tree의 조건이 되는 개념이다.
  • 높이(height) : Root node부터 시작하여, 가장 깊게 뻗어나가 있는 leaf node까지의 깊이를 말한다. root node는 높이를 0으로 둔다.
  • 레벨(level) : 각 층의 번호를 의미한다. root node의 level은 0이다.

따라서 위 tree의 차수는 2, 높이는 3이며, root node는 A다. 노드 C는 level 1에 있으며 parent node로 A를, child node로 leaf node인 F와 G를 두고 있는 internal node라고 이야기할 수 있다.

'컴퓨터 기초 > 자료구조' 카테고리의 다른 글

n-ary Tree와 Complete, Perfect, Balanced, Skewed Tree  (0) 2019.01.30
Tree의 표현(representation)  (0) 2019.01.21
Stack과 Queue  (0) 2019.01.16
Array와 Dynamic Array, Linked List  (0) 2019.01.11
ADT(Abstract Data Type)  (0) 2019.01.09

스택'자료구조'하면 대부분이 떠올리는 선형 자료구조이자 ADT이다. 둘 다 값을 삽입하는 push와 값을 제거하며 반환하는 pop 명령을 가지며(다르게 부르기도 함), 자료구조가 비어있는지를 확인하는 empty, 원소가 얼마나 들어 있는지를 확인하는 size 명령이 구현되기도 한다. 스택과 큐는 한가지 큰 차이점이 있는데, 스택은 LIFO(Last In, First Out), 큐는 FIFO(First In, First Out) 구조다. 얼핏 생각해 보면, 스택의 특징인 '가장 최근에 들어간 원소부터 꺼내진다'는 굉장히 쓸데없어 보이지만, 꽤 많은 곳에서 사용된다.

  • 함수의 call stack : 함수는 호출되었을 때 스택 프레임을 생성하여 call stack에 push되고, 함수가 종료되면 call stack에서 pop된다. 따라서, main -> a -> b -> c 순서로 함수를 호출하면, 순서대로 스택 프레임이 push되고, 가장 최근에 쌓인 스택 프레임부터 pop되어 c -> b -> a -> main 순서로 제어가 돌아온다.
  • 브라우저의 히스토리 : 브라우저는 사용자가 방문한 페이지들을 차례로 스택에 push한다. '뒤로가기' 버튼을 누르면, 히스토리에서 주소를 pop하여 보여준다.

큐는 백엔드를 경험해본 사람이라면 익숙하겠지만, 작업 큐에서 자주 쓰인다. 그 외에도 정말 많은 곳에서 쓰인다. 예를 들자면, 운영체제의 스케줄링 알고리즘 중 FCFS(First-Come, First-Served) 스케줄링도 큐(정확히는 큐의 메커니즘인 FIFO)가 쓰인다.

설명만 무작정 하면 재미 없으니, Go로 스택과 큐를 구현하는 것으로 글을 마무리한다. Slice를 단지 써먹은 데에 지나지 않으므로, 진지하게 구현하려면 메모리를 직접 할당하는 방식(C의 mallic, realloc 등..)을 사용하자.

'컴퓨터 기초 > 자료구조' 카테고리의 다른 글

Tree의 표현(representation)  (0) 2019.01.21
Tree  (0) 2019.01.18
Array와 Dynamic Array, Linked List  (0) 2019.01.11
ADT(Abstract Data Type)  (0) 2019.01.09
[자료구조] AVL Tree  (0) 2018.09.09

이번엔 API 설계 원칙과 직렬화 포맷을 결정한다. 대부분의 경우 이 두가지는 '당연히 REST랑 JSON 아님?' 하며 관례적으로 결정하고 넘어가곤 하지만, 빼먹지 말고 이것도 의사결정 과정을 끼워 두자. 사실 이 앞에 프로토콜을 결정하는 챕터를 넣어두려 했는데, 거기까진 너무 TMI인 것 같아서 나중으로 미뤘다. 프로토콜은 일단 현재로선 일반적으로 사용되는 HTTP/1.1을 사용하는 것으로 하자. 물론 추후에 HTTP/2에 대한 이야기도 할 예정이다.

API 설계 원칙의 도입 이유

우리는 결론적으로 웹 어플리케이션 서버를 개발하고 운영하는 것이 목적이므로, '이렇게 HTTP 요청을 보내면, 이렇게 응답해준다'라는 스펙을 기능에 따라 설계해야 한다. API 설계 원칙은 웹 서버의 API 스펙을 어떤 규칙에 따라 정의할 것인지를 나타낸다.

  • 'GET /post는 게시글 목록을 불러오고, GET /post/{id}는 특정 게시글의 내용을 불러온다'같은 설계는 다 아키텍처 기반으로 결정하는 것이 좋다. 아키텍처가 없다고 API 디자인을 못하는 것은 아니지만, 의사결정의 기반이 있는 것이 좋기 때문이다. 나는 옛날에 이런 걸 몰랐어서 모든 요청을 싹 다 POST /로 받고, 101, 102와 같은 동작 code를 가지고 로직을 분기시켰다. '미친놈인가'하는 소리 안 들으려면 아키텍처 베이스가 있어야 한다.
  • '잘 디자인된' API는 불필요한 커뮤니케이션 비용을 줄인다.

직렬화 포맷의 도입 이유

직렬화 포맷에 대해 예를 들면, '게시글'을 나타내는 '제목'과 '내용' 데이터를 표현하기 위해 아래와 같은 방법들을 사용할 수 있다.

데이터를 어떤 방식으로 표현할 지를 결정해 두어야, 클라이언트와 서버 간의 데이터 교환에서 혼란을 줄일 수 있다.

의사결정 - API 설계 원칙 결정

배경과 요구사항

  • 클라이언트 사이드에게 익숙한 아키텍처거나, 러닝커브가 감당 가능한 수준이어야 한다.
  • 개발 일정에 딜레이를 일으킬 여지가 있으면 안된다.

선택지

  • HTTP API
  • REST API
  • GraphQL

의사결정

HTTP API를 선택하겠다. 그 이유는,

  • REST API가 명시하는 모든 원칙을 만족하는 API를 작성하는 것은 쉽지 않다. 결국은 '느슨한 REST' 느낌의 HTTP API가 되기 마련이다. 따라서 괜히 RESTful API 이러면서 깝치다가 정의구현 당하는 수가 있다. 제약조건을 따르던지 다른 단어를 쓰도록 하자. REST의 self-descriptiveHATEOAS 원칙은 만족하기 정말 어렵다.
  • REST는 HATEOAS(hypermedia as the engine of application state)라는 원칙을 지켜야 하는데, 이는 '어플리케이션의 상태가 Hyperlink를 이용해 전이되어야 한다'라는 의미다. 우리의 API는 미디어 타입이 JSON(JSON 형식의 데이터를 위주로 주고받는 형태)일텐데, HATEOAS를 지키기 어렵다.
  • API가 꼭 REST API여야 할 필요가 없다.
  • GraphQL은 사례가 너무 적다. 사용자 인증 처리 로직을 생각하는 것부터 고민이 많아진다.
  • GraphQL은 클라이언트 사이드에서 러닝커브가 생길 수 있다.
  • HTTP API가 이야기하는 아키텍처로 충분할 것이라고 판단했다.

본인이 REST라는 걸 잘못 알고 있었나 싶다면, 그런 REST API로 괜찮은가라는 슬라이드 자료를 읽어보자.

준비

MDN의 HTTP 개요 문서, 이고잉님의 HTTP 강의 영상을 한번쯤 보면 좋을 것 같다. 'HTTP를 하나도 모르겠다'라면, 진도를 더 나가기 어려울 수 있으니 개발 잘하는 친구한테 물어보거나, 구글링을 하거나 해서 조금이라도 배경을 쌓고 넘어가자.

의사결정 - 직렬화 포맷

예를 들어, Kotlin로 이루어진 안드로이드 어플리케이션이 HashMap 객체를 문자열과 같이 특별한 형태로 가공해서 보내면, Node.js로 구성된 WAS가 이를 JavaScript 고유의 Object 타입으로 해석해 사용할 수 있어야 한다. 이를 위해 표준화된 직렬화 포맷이 여럿 존재하며, 그들 중 어떤 포맷을 사용할 것인지를 결정하도록 하자.

배경과 요구사항

  • 이것 또한 클라이언트 사이드에게 익숙한 아키텍처거나, 러닝커브가 감당 가능한 수준이어야 한다.
  • key-value 매핑array와 같은 요소의 나열 표현에 문제가 없어야 한다.
  • 충분한 수준의 데이터 타입을 커버할 수 있어야 한다. - 정수, 실수, 문자열, boolean 등

선택지

의사결정

JSON을 사용하자. 그 이유는,

  • 동일한 데이터를 표현하더라도, JSON이 비교적 더 잘 경량화되어 있으며 가독성도 좋다.
  • XML의 tree 구조는 자원을 표현하는 데에 그리 효과적인 포맷은 아닌 것 같다고 판단했다.(Array의 표현이 어려움) 옛날엔 많이 쓰였다고 하는데, 주관적인 의견을 덧대자면 진짜 이거 왜 쓰는지 모르겠다. 아직도 공공 API의 일부가 XML을 쓰는 게 정말 놀랍다.
  • YAML관례 상 직렬화 포맷으로 잘 사용하지 않는다. 역직렬화 속도도 느리고, YAML을 썼을 때 생기는 메리트가 딱히 없다.
  • Protobuf구글에서 개발한 data exchange format이다. 직렬화/역직렬화 속도가 빨라 성능 상의 이점이 있고, .proto 파일을 정의하는 것만으로 validation rule들을 정리하고, 비교적 적은 노력으로 API 문서화에도 응용할 수 있으며, 클라이언트 단은 proto 컴파일을 통해 이들에 대응되는 클래스(DTO)들을 자동으로 정의할 수도 있어서 시도해볼 가치가 충분하다. 그러나 조직 내에 protobuf를 사용했을 때 문제가 없을지에 대한 신뢰 수준이 낮고, 우리의 서비스 규모가 크지 않으므로 protobuf에 대한 확신이 생기고 난 후에 바꾸더라도 스트레스가 크지 않을 것이라는 생각이다. 당장은 다들 익숙해 하는 JSON을 쓰고, 나중에 바꿔 보자.
  • JSONJavaScript Object Notation의 약자다. JavaScript나 그 형제들(TypeScript 등)로 로직 처리를 하게 될 프론트엔드에게 JSON만큼 편한 구조가 없으며, 모바일 앱과 웹을 포함해 대부분의 프론트엔드 엔지니어들은 이미 JSON에 익숙해져 있다.

준비

Stackoverflow의 'What is JSON and why would I use it?이라는 질문과, json.org의 JSON 개요 문서를 한 번 읽어 보자. 이번 챕터에서는 의사결정에 관례의 비중이 컸다. 기술 선택에 너무 의외성이 크면 조직 전체에 걸쳐서 기술부채가 생길 수 있어서, 관례를 감안할 수밖에 없었던 것 같다.

지금까지

  • 버전 관리 시스템으로 Git을 사용하기 시작했다.
  • Git 웹호스팅 서비스로 GitHub를 사용하기 시작했다.
  • GitHub Issues와 Projects로 이슈 트래킹을 시작했다.
  • 개발 프로세스와 브랜칭 모델을 정립했다.
  • HTTP API 아키텍처 기반으로 API 스펙을 디자인하기로 했다.
  • JSON을 직렬화 포맷으로 결정했다.


+ Recent posts