캐시란 데이터를 임시로 저장해두는 장소를 말한다. 임시로 저장하여 사용하는 데이터의 종류는 제한이 없기 때문에 작게는 메모리에서 크게는 Logic 혹은 그 이상을 저장할 수 있다.


Cache 의 목적은 로직을 처리하는 데 있어서 빠른 접근성을 제공하는 것이며, 단순히 수동적으로 보관하는 것에 그치지 않고 이를 응용해서 작업의 결과를 저장함으로써 해당 로직의 불필요한 수행을 줄여주는 능동적인 역할까지 수행한다.


캐시가 될 수 있는 것은 일반적으로 로컬 메모리부터 별도의 디스크 볼륨까지 다양하지만 Cache 로 사용하기 위한 가장 중요한 요건은 데이터로의 접근성이다.


Cache 에 대한 접근성은 어떤 경우에도 로직 상에서 원하는 데이터에 직접 접근하거나 만들어내는 비용보다 저렴해야 한다. 그래야만 캐시로서의 의의가 있는 것이다.


그렇기 때문에 Cache 는 접근성이 빠른 공간(Space)에 빠른 자료구조를 사용한다.


캐시서버로 이용되는 서버들은 I/O 에 최적화된 공간이 사용되며 당연히 이에 대한 접근성에 있어서 효율적인 REST 등의 방법을 사용한다. 


컴퓨터 내에서 사용되는 캐시는 RAM보다 빠른 L1,L2 레지스터를 캐시로 사용하고, 프로그램 내에서 구현된 Software Cache 라면 접근에 용이한 Map 과 같은 자료구조에 인메모리(Inmemory)로 저장한다.


다음은 캐시를 이해하는 데 중요한 용어들이다.


 - origin : origin 혹은 origin server 는 캐시에 저장할 실 데이터가 존재하는 공간이다. 웹 캐시라면 DB 서버일 수도 있고, SW 내에서라면 파일 혹은 실행 함수 그 자체일 수도 있다.


 - cache expire : 프로세스 내에서 사용하는 인메모리 캐시나 영구히 상주해야하는 정보를 가진 캐시가 아니라면 Cache 는 Expire Date 를 갖고 있으며 해당 시간이 지나면 상한(Stale) 상태가 된다.


 - cache freshness : 캐시가 만료되지 않은 경우를 fresh 한 캐시라 하고 만료된 경우 stale cache 라 한다.


 - cache hit : 참조하려는 데이터가 캐시에 존재할 때 해당 캐시를 조회하는 걸 Cache hit 이라 한다.


 - cache miss : 참조하려는 데이터가 캐시에 존재 하지 않는 경우


 - cache hit ratio : 적중률로 전체 참조 횟수 대비 Cache hit 된 비율을 의미한다. 실질적으로 캐시의 설계는 Cache hit Ratio 를 높이는 데 초점을 둔다.



다음은 캐시의 동작에 대한 정책들이다.


 Cache Read : 


 - Cache-aside 방식 : 데이터를 참조 하기 전에 참조하고자 하는 값이 캐시에 존재하는지 확인한다. 여기서 값을 직접 비교하기 보다는 키를 이용해서 캐시에 접근한다. 

 Cache에 존재한다면 Cache에서 데이터를 가져온다. 만약 Cache에 존재하지 않는다면 origin에서 데이터를 가져오고 이를 캐시에 저장한다.


 - RT/WT/Write back 방식 : 캐시를 Main Data Source 로 사용하기 때문에 캐시에서만 데이터를 조회한다.

 RT/WT 방식(Read Through / Write Through) 은 Read Scalability 가 가장 뛰어나다.



 Cache Write :


 - Cache-aside 방식 : 캐시를 Application Level 에서 직접 갱신시켜준다. 개발자가 Flow 를 이해하고 Update / Evict 시켜줘야 하며, 그렇지 않으면 Cache 데이터와 DB 데이터가 불일치하는 Stale 현상이 발생한다.


 - Read Through / Write Through 방식 : 데이터의 쓰기시 캐시와 실제 저장공간의 데이터 둘다 최신화 시키는 작업이다. 

 캐시를 메인 Database 로 사용하는게 특징적이며, 캐시에 데이터를 먼저 업데이트하고 캐시에서 Main Database 를 즉시 갱신시킨다.

 양쪽의 데이터를 동일하게 유지할 수 있지만, 쓰기 시에 추가 부하가 생긴다는 단점이 있다.


 - Write Back 방식 : 데이터의 쓰기시 캐시의 데이터만 최신화 하고 해당 Cache 를 Evict 시켜놓는다. 이 후 RT/WT 방식처럼 캐시 값을 마킹된 기준으로 origin 으로 직접 반영(Write) 하는데, 캐시가 별도의 큐를 이용해서 Database Source를 비동기로 Update 시켜준다.

 Write Performance 와 DB Scalability 에 있어서 가장 뛰어나다.

 쓰기 작업이 Cache 에서만 발생하지만, Cache 가 만료되는 시점까지 Origin 에 Write Failure 가 발생한다면 데이터를 영구 손실할 위험이 존재한다. 



 Cache Replacement


  웹 캐시의 경우 자동 expire 하거나 명시적으로 cache 를 지워주는 동작을 해주지만, 캐시를 Scheduling 에 사용하는 컴퓨터나 알고리즘의 경우 Replicement Policy 를 갖는다.



 다음은 몇가지 대표적인 알고리즘들이다.


  - FIFO(First In First Out) : 오래된 캐시를 먼저 비우고 새로운 캐시를 추가하는 방식이다.


  - LIFO(Last In First Out) : 가장 최근에 반영된 캐시가 먼저 지워진다.


  - LRU(Least Recently Used) : 가장 최근에 사용되지 않는 순서대로 캐시를 교체한다. 가장 오랫동안 사용되지 않은 캐시가 삭제되며 일반적으로 사용되는 방식이다.


  - MRU(Most Recently Used) : 가장 최근에 많이 사용되는 순서대로 캐시를 교체한다. 휘발성 메모리를 이용해야 하는 특수한 상황에 사용된다.


  - Random : 말그대로 랜덤으로 캐시를 교체한다.


 운영체제를 배웠다면 페이지 교체 알고리즘이 Cache Replacement 정책을 사용한다는 것을 알 수 있을 것이다.



본 포스팅에서는 넓은 범위의 Cache 의 정의와 목적, 정책들에 대해 정리해보았다.


이론적인 부분이고 웹 캐시와는 조금 다르기도 하지만 중요한 기본 개념은 잘 숙지해두자.


참조 : 

https://en.wikipedia.org/wiki/Cache_replacement_policies

https://codeahoy.com/2017/08/11/caching-strategies-and-how-to-choose-the-right-one/

https://gomguard.tistory.com/115

https://onecellboy.tistory.com/260

https://dzone.com/articles/using-read-through-amp-write-through-in-distribute



CQRS(Command and Query Responsibility Segregation) 란 .Net 기반으로 발전되고 있는 설계 방법론으로 명령과 쿼리의 역할을 구분하는 것이다. 


이는 데이터에 대한 조작 Create, Insert, Delete 와 데이터에 대한 조회 Select 를 구분하는 것에서 출발한다.


어플리케이션을 개발할 때, 컨텐츠를 위한 데이터 모델은 계속해서 복잡도가 올라가게 된다.




특히 주로 사용되는 위의 모델처럼 데이터 변경과 조회는 보통 하나의 데이터모델을 사용하게 되는데, 어플리케이션의 복잡도가 증가할 수록 각 API 기능의 책임이 어떤 데이터 모델에 있는지는 불분명해진다.


이는 설계에 있어서 초기 의도를 지워버리는 역할을 하며 많은 경우의 레거시 코드들이 이런 기반으로 생겨나게 된다.


CQRS 는 이러한 고민에서 출발하며, 데이터에 대한 조회(Query) 와 데이터에 대한 조작(Command) 을 분리함으로써 이 문제를 해결하고자 한다.


기본적으로 CQRS 를 적용하기 위해서는 Command 을 위한 도메인 모델과 Query 를 위한 도메인 모델을 분리한다.



분리된 각각의 도메인 모델을 DB에 적용하는 방안으로는 몇가지가 있다.


(1) Simple


 : 같은 Scheme 을 가진 DB를 사용하며, Command / Query 시에 데이터에 대한 Converting 을 거친 후 DB에 CRUD 에 대한 작업을 수행한다.

 이 경우에는 일반 어플리케이션과 같으며 도메인 모델만 분리한 상태로 개발이 쉽고 적용이 간단하다.



(2) Premium


 : Command 를 위한 DB와 Query 를 위한 DB를 분리하는 형식으로, 데이터의 정합성을 위한 RDB를 Command 용 DB로 분리하고 Query 가 간편한 NoSQL 을 Query 용 DB로 주로 사용한다.

 이렇게 동일한 데이터에 대해 다수의 저장소를 운용하는 방식을 Polyglot Storage 라 하며 이 경우 용도에 맞는 저장소를 골라서 좀 더 알맞게 사용이 가능하다.

 하지만 분리된 저장소 각각에 대한 데이터 동기화 이슈를 Broker 등을 이용해 처리해주어야 하는 점은 이슈가 되며 책임의 소재나 로깅 등에 있어 신뢰도 확보를 위한 작업이 필요하다.



(3) Event Sourcing


 : Event Sourcing 이란 Application 내의 설계를 컨텐츠 기반이 아닌 기능 기반으로 하면서 이러한 "이벤트(Event)" 자체를 DB에 저장하는 방식을 말한다.

 이렇게 함으로써 이벤트에서 사용하는 도메인 모델은 컨텐츠를 위한 DB에 Write 되고 Query 시에는 이벤트를 저장한 DB로부터 해당 컨텐츠를 바탕으로 데이터를 만들어서 가져온다.

 도메인 모델에 대한 Command 가 따로 저장되고, Query 를 위한 도메인 모델은 Event DB로부터 불러오는 방식 때문에

 Event Sourcing 의 Architecture를 적용함에 있어서 CQRS 는 필수적인 설계 방식이 된다.

 CQRS 를 적용하는 데 있어서도 가장 큰 시너지를 낼 수 있는 Architecture 의 하나이다.


<향후 Event Sourcing Architecture에 대해서는 추가로 정리한다.>


CQRS 가 실무에 적용되는 데 있어 아직은 국내외적으로 불확실성이 있는 듯 하지만, 주목해볼만한 패턴인 것은 틀림없다.


(참고자료 : https://docs.microsoft.com/en-us/previous-versions/msp-n-p/dn568103(v=pandp.10))



Protocol Buffer 는 구글이 만든 언어 및 플랫폼 중립적이고 확장성을 갖춘 새로운 형태의 직렬화 매커니즘이자 데이터 포맷이다. 

 

XML과 유사하지만 더 작고 빠르며, 포맷을 정의하기만 하면 컴파일을 통해서 C++ / C# / Java / Python / Go / Ruby 등의 언어에 대해서 바로 코드형태로 바꿀 수 있다. 


프로토버프의 파일 포맷은 .proto 이며 이 안에 자료 구조를 정의하면 프로토버프 모듈을 통해 각 언어에 맞게 컴파일을 한 뒤 사용하는 방식이다.

언어에 무관하게 포맷만 서로 공유가 되면 각 언어에 맞는 모듈들이 알아서 Serialize / Deserialize 를 해주기 때문에 쉽게 사용할 수 있다.


.proto 의 샘플 데이터 포맷은 다음과 같다.


syntax = "proto2";
option java_package = "com.model.protobuf";

message SimpleProtoBufMessage {
required int32 id = 1;
required string message = 2;
enum MessageType {
PING = 0;
REQUEST = 1;
RESPONSE = 2;
}
optional MessageType messageType = 3 [default = PING];
}


 syntax 는 컴파일할 대상 protobuf 버전을 지칭하며 java_package 와 같은 것들은 option 으로 지정되어 자바로 컴파일 시에만 적용이 된다.

위의 message 는 클래스로 매핑이 되며 그 안의 멤버변수들은 1, 2, 3 과 같은 연속된 숫자가 값으로 할당되는데 이는 직렬화 순서를 의미한다.


protobuf 2 버전과 3 버전에는 몇가지 문법에 있어서 차이가 있다.

예를 들어 위의 예제 양식에서 required 는 proto3에서 없어졌고, optional 은 proto3에서 기본형이라 역시 사라졌다. 

Default 값 지정도 proto3 에서 사라진 내용으로, protobuf 의 현 방향은 기본값을 배제함으로써 null 체크에 대해 좀 더 엄격해진 모습이라고 이해하면 된다.


위의 파일을 proto3 버전으로 바꾼다면 다음과 같이 된다.


syntax = "proto3";
package PB.Simple;
option java_package = "com.model.protobuf";

message SimpleProtoBufMessage {
int32 id = 1;
string message = 2;
enum MessageType {
PING = 0;
REQUEST = 1;
RESPONSE = 2;
}
MessageType messageType = 3;
}



.proto 를 컴파일 하기 위해서는 protobuf 모듈을 OS에 맞게 다운받아서 protoc 모듈을 이용하면 된다.


위와 같이 생성된 .proto 파일을 protoc 모듈을 이용해서 각 언어별로 컴파일을 수행하면, 다소 복잡한 형태의 코드를 얻을 수 있으며 구글에서는 최대한 이 파일을 변형하지 않고 사용하길 권장한다.


<proto 컴파일된 Class 파일 예시>



위처럼 생성된 파일은 해당 언어에서 바로 모듈 혹은 클래스의 형태로 이용이 가능하다. 


대게 프로젝트에서는 직접 이용하기 보다는 별도의 Converter 를 만들어두고, 통신시 혹은 직렬화시에만 Convert 해서 사용한다.


프로젝트 Protobuf 로 통신은 application/x-google-protobuf 와 같은 새로운 형식이며 서버 클라이언트간 통신 시 이 형태로 consume 및 produce 가 일어나야 한다.

이때, 서버와 클라이언트는 동일한 .proto 파일을 공유하기만 하면 각기 환경에 알맞게 컴파일해서 내부 클래스로 사용하면 된다. 

주로 REST 서버로 POST 에서 RequestBody 에 protobuf 모델이 담겨서 통신하게 된다.


통신에 있어서 특징적인 점은, Protobuf 통신시에 값이 default 값과 같다면 통신 시 전송하지 않는다는 점이다. 

이는 프로토버프가 프로토콜 설계시 성능을 크게 고려했다는 점을 알 수 있는 부분이다.


실무에서 사용 결과 JSON 보다 확실히 우월한 퍼포먼스를 내는 것을 실감하고 있다. Json 에 비하자면 아직은 쓰기 불편하지만, 익혀둘만한 기술임은 틀림없다.




YAML 은 JSON이나 XML과 마찬가지로 설정 파일 등의 목적을 가진 데이터를 기술하기 위해 만든 포맷으로 JSON과 비교하자면 좀 더 구조가 복잡하지만 사람이 보기에 가독성 측면에서 좀 더 자연스러운 포맷이다. 


YAML 은 E-mail 양식에서 아이디어를 얻어 읽기 쉬운 데이터 포맷으로 고안되었으며 문서 Markup Language 가 아닌 데이터 표현을 위한 가벼운 형태의 언어이다.


JSON과는 다르게 자료형을 정의하게 되어있으며 기본은 primitive 형이다. 

Scalar 라고 하는 숫자 / String 형태, Sequence 라 하는 배열/리스트 형태, Mapping 이라 하는 key-value pair 형태 등이 존재하며 #은 주석이다. 

Space를 이용한 들여쓰기로 구조체를 구분한다. (Tab이 아니다.)


하지만 프로그래밍 언어처럼 반드시 문서에 형을 명시해야하는 것은 아니고, 다음과 같이 분류만 해주면 된다.


integer : 100

string : "100"

float : 100.0

boolean : Yes


다음은 간단히 만들어본 YAML 파일 예시이다. (에디터의 사소한 플러그인 에러로 인해 보이는게 이상하지만 신경쓰지말자 ;;)


---
# List of pets
pets:
- dog
- cat
- bird
- fish

# pets in shop (list inline)
shops: ["dog", "cat"]

# Dictionary of owner's pet (hashtable inline)
map: { james: dog, jenny: cat }

# Dictionary of pet's owner
dog:
- james,
- tom,
- kate
cat:
- jenny

pageNo : 10
valid : Yes



YAML 은 몇가지 지시자를 사용하여 특정 작업들을 수행할 수 있다.

가령 %YAML 문자는 문서의 YAML 버전을, %TAG 지시자는 URI 주소를 나타낸다.


근래에 많은 오픈소스들의 YAML 을 활용하고 있고, 커뮤니케이션 포맷으로도 사용되는 것 같다. 알아두는 것이 좋을 것 같다.




분산 파일 시스템(Distributed File System)은 이더넷 네트워크에 연결된 Shared File System 으로, 주로 네트워크 상에 존재하는 여러 Shard File System 들을 클러스터링하여 Virtual File System 형태로 제공하는 것을 말한다.

그렇기 때문에 Block level Access가 아닌 network protocol 로 각 파일시스템에 내부적으로 접근하게 된다.

 

주로 여러 네트워크로 연결된 파일시스템들이 공유폴더를 구축하고, 이를 DFS 가 Managing 하여 클러스터링하는 형태이다.
이 때, 연결된 각 File System 서버들의 파일들은 Globally Unique 하게 관리되며, DFS 서버에 장애가 발생할 경우 DFS 를 사용할 수 없게 되어있다.

 

그렇기 떄문에 DFS 파일 시스템은 서버 내에서 각 클러스터된 파일시스템들에 대한 Fault-Tolerance 를 관리해주게 되고, 때때로 고성능을 내기 위해 Parallel 한 클러스터를 구축하기도 한다.

 

Distributed File System 과 Distributed Data Store 의 차이는 DFS는 로컬에서 파일을 접근하는 것과 마찬가지의 Interface 를 제공한다는 점이다.
즉, 사용자는 실제 파일 시스템을 다루듯 디렉토리 리스팅, 파일 실행, 파일 삭제 등을 할 수 있다.

 

DFS 가 제공하는 이점은 다음과 같다.

 

- Data migration 의 단순화


- 데이터 사용의 용이함


- 보안상 관리의 이점


- 서버의 고가용성 확보

 

 

이러한 Distributed File System 은 일반적으로 사용할 때에는 중앙 DFS 서버에서 관리하나, 클라우드에서 프로비저닝 될 때에는 하나의 서비스 형태로 제공된다.


대표적으로 Amazon S3, Google Cloud Storage, Openstack SWIFT, Microsoft Azure 가 있으며 모두 HTTP 프로토콜을 이용한 REST API 형태로 인터페이스를 제공한다.

 


* NFS 역시 분산 파일시스템의 일종이다.


NFS(Network File System) 는 네트워크에 파일을 저장하는 메커니즘이다. 

원격 컴퓨터에 있는 파일 시스템을 마치 로컬의 파일시스템처럼 사용할 수 있도록 허용하는 분산 파일 시스템이다.


NFS File System 은 서버와 클라이언트로 이루어져있고, 일반적인 로컬 파일시스템과 똑같은 기능을 수행할 수 있는 인터페이스를 제공한다.




컴파일 과정은 사람이 이해할 수 있는 High Level Programming Language 로 구성된 소스코드를 기계가 이해할 수 있는 Lower Level Language 로 바꾸는 과정이다.


컴파일러는 다음과 같은 과정을 통해 컴파일을 수행한다.


[출처 : https://www.programcreek.com/2011/02/how-compiler-works/]


위의 그림은 컴파일의 단계를 간략하게 설명한다. 다음은 그림에 대한 설명이다.


(1) Lexical Analysis 

소스코드를 Token 으로 분할한다. 모든 키워드와 Parenthesis, 변수들 및 괄호들을 분리해낸다.


(2) Syntax Analysis

앞선 단계에서의 스캔으로 만들어진 토큰들(Token Stream)의 문법을 분석하기 위한 자료구조료 변형한다.

이렇게 만들어지는 자료구조를 Parse Tree 라고 한다.

이 단계에서는 Token 이 Valid 한지 검출하지 못하며, Token 이 사용되기 이전에 정의 또는 초기화되어있는 지 등 정적분석은 불가능하다.


또한 이단계에서 파싱이 일어난다. Parsing 작업은 Top-Down, Bottom-Up 두가지 방식으로 나뉜다.

Top-Down Parsing 은 Parse Tree 의 윗쪽부터 파싱을 수행하며, Bottom-Up Parsing 은 트리의 아래쪽부터 파싱이 수행된다.


유명한 파서의 종류로 Top-Down 방식의 Non-Backtracking Predictive Parser 인 LL Parser와 Bottom-Up 방식의 LR Parser 가 존재한다.


(3) Semantic Analysis

각 기호들 및 구문들을 의미있는 값들로 변경한다. 가령 기호 < 는 bool 을 Return 하는 Operand 함수로,

While 과 같은 키워드는 반복 구조를 이루는 void 함수로, 각 변수는 메모리로 치환한다.

이 단계에서 Type 의 Mismatch 나, 변수의 미정의, 파리미터 미정의 등 문법적 요소들이 검증된다.


(4) IR Generation

구문 분석으로 이루어진 자료를 중간 언어로 변경하는 작업을 수행한다.

IR 은 Intermediate Representation 의 약어로 소스코드에 근접한 기계어인 High Level IR, 타겟 머신에 종속적으로 디자인된 Low Level IR 이 존재한다.

컴파일러는 소스코드를 High Level IR -> Low Level IR 로 변경한 뒤 Target Machine Code 로 해석한다.


(5) IR Optimization

중간 언어를 최적화한다. 불필요한 루프를 없애거나, 사용하지 않는 변수의 정리 등이 수행된다.


(6) Code Generation

Syntax Analyzer 및 Semantic Analyzing 된 출력값을 Low level Code 로 해석한다.

이 단계를 거쳐서 Assembly Code 등의 Object Code 로 번역된다.


이 단계에서 수행되는 작업들은 다음과 같다.

 - Instruction Selection : 어떤 명령어를 사용할 것인지

 - Instruction Scheduling : 어떤 명령어를 먼저 실행할지 (최적화)

 - Register Allocation : 변수들을 프로세서 레지스터에 할당

 - Debug data generation : 디버그 모드인 경우 디버그를 위한 코드를 생성


(7) Optimization

위의 과정에서 생성된 코드를 한단계 더 최적화한다.

이 단계의 최적화는 두단계로, Machine 에 종속되지 않은 일반적인 형태의 최적화와 Machine 에 종속된 최적화가 이루어진다.

이 과정을 거치면서 중복 제거, 메모리 확보, 코드 정리, 루프 최적화, Control Flow 개선이 일어난다.


위의 단계들을 거치면 High Level Source Code 는 Machine Code 로 변환된다.

Generating 되는 코드는 대게 머신별로 다르게 되며, 세부로직 및 최적화의 과정 역시 컴파일러에 따라 차이가 존재하게 된다.




본 포스팅은 컴파일러에 대한 간략한 소개를 다루고 있으며 더 자세한 내용은 다음 링크들을 참조한다.

(이해해야할 분량이 많다.)


참조

https://www.tutorialspoint.com/compiler_design/index.htm

https://www.programcreek.com/2011/02/how-compiler-works/



 마틴 파울러가 재창한 두개의 개념으로, 비즈니스 로직을 처리하는 두가지 패턴을 말한다.

이는 책임지는 쪽이 Domain Level이냐 Script Level이냐에 따라 구분된다. 가령 다음과 같은 예시가 있다고 가정해보자.


먼저 트랜잭션 스크립트란, 은행의 계좌 이체 서비스처럼 하나의 트랜잭션 안에서 필요한 모든 로직을 수행하는 패턴이다. 

그래서 이름도 Transaction Script 로 부른다. 구현이 매우 쉽고 단순하지만 구조가 복잡해질 수록 모듈화의 복잡도 역시 높아진다.

다만, 하나의 강건한 로직이 해당 모듈에서만 구현되어야할 경우 side effect 를 예방할 수 있고, 좀 더 효율적인 코드를 작성할 수 있다.


도메인 모델은 우리가 흔히 객체 지향의 예제로 많이 배우는 형태의 모델로, 각 객체에 객체가 수행해야 하는 업무를 분담시키는 것이다. 

주요 특징은 데이터와 프로세스가 같이 존재한다는 점이며, 객체간 관계를 맺을 수 있어, 제약하거나 로직의 단순화에 도움이 된다.


또한 주로 같이 사용되는 상속이나 다양한 패턴들이 객체를 중심으로 얽히면서 개발자가 선택할 수 있는 유연성이 높아지고 모듈화가 간단해진다. 유지보수가 편리하며 재사용성이 뛰어나다.


 하지만 객체들간의 설계가 수반되어야하기 때문에 모델 구축이 쉽지 않고, 객체간의 관계를 잘 풀어나가야 한다. 특히 객체간의 dependency 는 반대로 말하자면 제약사항이기 때문에 설계가 매우 중요하다.


도메인 모델과 트랜잭션 스크립트 패턴의 차이를 보기 위해 다음 예제를 확인해보자.



    Emp emp = loadEmp("나직원");
    Dept dept = loadDept("A부서");



이 때 이를 비교 하기 위한 Domain Model 방식과 Transaction Script 방식의 차이는 다음과 같다.



    //Transaction Script 방식
    if(emp.getDeptName() == dept.getDeptName())
        return true;

    //Domain Model 방식
    if(emp.isBelongTo(dept))
        return true;



즉, Function의 책임 소재를 어디에 두는가, 사용자가 작성하는 코드 로직에 두는가(Transaction Script 방식) 아니면 객체 자체의 모델링 자체에 두는가(Domain Model 방식)의 차이이다.


참조

http://javacan.tistory.com/entry/94

http://lorenzo-dee.blogspot.com/2014/06/quantifying-domain-model-vs-transaction-script.html






data에 대한 데이터를 말하는 것으로, 다른 데이터를 설명해준다.


 정보를 효율적으로 찾기 위해서 일정한 규칙에 따라 컨텐츠에 부여되는 데이터이다.


 데이터 자체를 좀더 적은 정보량으로 표현하기 위하여 만들어졌으며, 이는 인터넷에서 문서들을 찾기 위한 일종의 태그 역할을 수행하기도 한다.


 즉, 메타데이터를 기준으로 문서를 구분할 수 있는 일종의 Unique 한 Key 역할을 할 수 있으며 이로 인해 주로 분석이나 분류 목적으로 따로 쓰이기도 한다.


실무에서도 주로 서버를 구성할 때, 관리하는 데이터는 이 meta data가 되며, meta data를 이용한 참조 혹은 인덱스로 실제 데이터를 가리키는 데 사용한다.


메타 데이터의 쓰임새는 워낙 무궁무진하며 혹자는 메타 데이터에 대한 설계 능력이 data를 설계하는 중요한 역량이라고 말하기도 하더라.


* 실무에서도 많이 쓰이는 용어이기 때문에 간단하지만 알아둘 필요가 있다.



회사마다 사용하는 테스팅의 종류가 다를 수는 있으나, 여지껏 다니던 회사들에서 주로 사용하던 테스팅 방법 들을 정리해보았다.


회사마다 QA 팀이 갖추어져 있는 경우도 있고 스타트업의 경우 별도의 QA팀 없이 개발팀에 해당 책무가 주어지는 경우가 있다.


다음은 몇가지 테스팅의 종류들이다.


(1)   Sanity Testing : 새로운 버전이 주요 테스트를 수행하기 적합한지를 판단하기 위한 테스팅. 만약 사용 초기의 Crash 등으로 프로그램이 사용불가능하다면 시스템은 테스팅이 불가능하다. , QA 위한 테스트라 있다.


(2)    BlackBox Testing : 내부설계 구현은 고려하지 않고 요구사항과 기능성에만 기반한 테스팅을 수행한다.


(3)    WhiteBox Testing : 글래스박스 테스팅이라고도 하며, 내부로직에 대한 지식을 기반으로 코드구문, 분기, 조건 등에 대한 커버리지를 포함한다.


(4)    Unit Testing : 개발자에 의해 수행되며 소프트웨어 모듈 각각에 대한 세부적 테스팅


(5)    End to End Testing : 주로 네트워크, DB 연동, 시스템간 커넥션에 대한 테스팅이다.


(6)    Load Testing : 부하가 걸린 상황에서 시스템의 동작을 점검한다.


(7)    Stress Testing : 허용 범위를 초과하여 한계치를 측정하는 테스팅이다.


(8)    Alpha Testing : 개발 부서 내에서 만든 가상환경에서의 테스팅


(9)    Beta Testing : 상용화 릴리즈 이전 end user 의해 완료되는 테스팅


(10)  Recovery Testing : 시스템의 회복을 테스팅한다.


(11) Smoke Testing : Sanity Test와 유사하게 테스트가 가능한지 여부를 판단한다. (연기가 나는지를 판단하는 작업) 여러 개의 스모크 테스트를 스위트라고 한다.


 * Sanity Test와 Smoke Test 의 차이는 기능 검사에 있다. Sanity Test가 새로 테스트 항목에 추가된 추가된 기능 / 수정된 버그 에 초점을 둔다면 Smoke Test 는 프로그램의 핵심적인 부분을 같이 검사한다. (즉, 새로운 기능추가가 기존의 핵심 기능에 영향을 안미치지는지 테스트 한다.)

또한, 같은 QA 이전의 테스팅이지만 주로 Smoke Test 는 개발팀 내에서 직접, Sanity Test 는 개발팀에서 기능 검사만 하고, QA팀으로 넘기는 경우가 많다.

(보다 자세한 비교는 다음 링크가 잘 설명해주고 있다.)

 - 참조 : https://www.guru99.com/smoke-sanity-testing.html




마이크로 서비스 아키텍처 (Micro Service Architecture) 란, 최근에 각광받고 있는 웹 기반 분산 서비스 시스템 아키텍처를 말하며, 이러한 아키텍처를 갖는 서비스 자체를 마이크로 서비스 (Micro Service) 라 한다.


앞선 포스팅에서 언급한 모놀리식(Monolithic) 아키텍처가 하나의 어플리케이션 또는 서비스가 여러개의 모듈이 결합된 강건한 형태의 아키텍처를 갖는다면, 마이크로 서비스 아키텍처는 반대로 독립된 각각의 모듈을 조립하여 만드는 하나의 서비스를 위한 아키텍처라고 볼 수 있다.

(참조(모놀리식 아키텍처) : http://jins-dev.tistory.com/entry/%EC%A0%84%ED%86%B5%EC%9D%98-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%AA%A8%EB%8D%B8-%EB%AA%A8%EB%86%80%EB%A6%AC%EC%8B%9DMonolithic-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98?category=760149)


즉, Software 설계 시에 Loose Coupling 을 위하여 고려되는 "모듈화" 의 개념이 Web API 를 이용하여 서비스 레벨로 확장된 것으로 이해하면 편하다.


이는 오래된 개념이며, 최근에 클라우드 시스템 및 도커 등의 발달로 인해 보다 손쉬운 구현이 가능해지면서 대세로 자리잡고 있다.


마이크로 서비스 아키텍처는 작은 서비스들의 컬렉션으로 구성된다. 각각의 작은 서비스 단위들은 단일 비즈니스 기능을 구현할 수 있어야 한다.



 마이크로 서비스 아키텍처에서 서비스는 작고 독립적이며 느슨하게 결합되어 있다. 각 서비스는 작은 개발 팀이 관리할 수 있는 개별 코드 베이스이다. 서비스들을 독립적으로 배포할 수 있으며 팀이 전체 응용 프로그램을 다시 빌드한 후 재배치하지 않고도 기존 서비스를 업데이트 할 수 있다


서비스는 해당 데이터 또는 외부 상태를 유지해야하고 이는 별도의 데이터 레이어를 갖는 기존의 모델과 다른점이다. 각 서비스들은 API를 사용하여 통신하고, 구현 내용은 감춰진다. 각 서비스들은 동일한 기술 스택, 라이브러리 또는 프레임워크를 공유할 필요가 없다.


 마이크로 서비스 아키텍처는 노드 간의 서비스들을 관리할 수 있는 특징적인 구성요소를 지닌다. 또한 서비스 목록을 조회하거나 클라이언트로부터 들어오는 API의 진입점을 별도 인터페이스의 게이트웨이로 분리하는 특징을 지닌다. 이는 API 서버일수도, 어드민 형태의 웹페이지일 수도 있다.


 마이크로 서비스 아키텍처가 사용되는 경우는 빠른 릴리즈 개발 속도가 요구되거나 고확장성이 필요한 복합적인 프로그램, 많은 하위도메인을 가진 거대한 프로그램 또는 소규모 개발 팀으로 구성된 조직에 유용하다. 이는 다음과 같은 이점들로 인해 기인한다.


-      독립배포 : 전체 프로그램을 다시 배포하지 않고도 업데이트가 가능하다. 이에 따라 버그 수정 및 릴리즈 관리가 용이하고 위험부담이 덜하다.


-      독립개발 : 독립적인 개발팀들에 의해 개발될 수 있다.


-      집중화된 소규모팀 : 팀이 각 서비스에만 집중할 수 있다.


-      결함격리 : 한 서비스가 다운되더라도 전체 서비스에 영향을 미치지 않는다.


-      혼합기술스택 : 각 서비스에 적합한 기술을 선택하여 조합할 수 있다.


-      세분화된 확장성 : 서비스를 독립적으로 확장할 수 있다. 이에 따라 리소스의 유연한 운용이 가능하다.


 위와 같은 장점을 갖고 있음에도 아직 마이크로 서비스 아키텍처는 모놀리식 아키텍처에 비해 복잡하며 독립된 구조로 인해 통합적인 유지 관리가 어려워질 수 있다. 가령 에러가 난 서비스의 권한이 다른 팀에 있다면, 그 부분의 보수를 위해서 우리가 직접 수정하는 것이 아닌 커뮤니케이션이 필요하게 된다.


또한 서비스 구성에 있어 네트워크 정체 및 통신에 신경을 써줘야 하며 데이터 일관성 및 버전 관리가 중요해진다. 그리고 프로젝트 진행에 있어 팀원들이 이루는 문화가 중요해진다. 이러한 기조에서 알맞은 개발 문화가 개발자가 운영까지 담당하는 DevOps(데브옵스) 문화라 할 수 있다.

 

 


+ Recent posts