C4 Model Diagram 'With PlantUML'

댓글 0
댓글을 작성하려면 로그인이 필요합니다.
아직 댓글이 없습니다. 첫 번째 댓글을 작성해보세요!

댓글을 작성하려면 로그인이 필요합니다.
아직 댓글이 없습니다. 첫 번째 댓글을 작성해보세요!
다이어그램을 그리는 데 있어서 가장 어려운 점은 무엇일까. 개인적으로는 "어느 정도까지 상세하게 그려야 하는가"라는 고민이 가장 크다. 너무 추상적으로 그리면 전달력이 떨어지고, 너무 상세하게 그리면 복잡해져서 오히려 이해하기 어려워진다.
C4 Model은 이러한 고민을 해결하기 위해 만들어진 소프트웨어 아키텍처 다이어그램 방법론이다. 시스템을 4가지 레벨(Context, Container, Component, Code)로 나눠서 표현하며, 각 레벨마다 적절한 추상화 수준을 유지하도록 돕는다.
C4 Model은 소프트웨어 아키텍처를 계층적으로 표현하는 다이어그램 방법론이다. 구글 지도를 생각해보면 이해가 쉽다. 처음에는 넓은 지역을 보여주다가, 확대할수록 더 상세한 정보를 보여주는 방식이다.
C4 Model은 이름에서 알 수 있듯, 4가지의 키워드로 소프트웨어를 구성하는 요소를 표현한다
본격적으로 각 레벨을 설명하기 전에, 실제로 다이어그램을 어떻게 그리는지 알아야 한다. C4 Model을 그리는 방법은 크게 두 가지다.
마우스로 드래그 앤 드롭하여 그리는 방식이다. 직관적이지만, 버전 관리가 어렵고 협업 시 충돌이 발생할 수 있다.
이 글에서는 PlantUML 기반으로 설명하겠다. 코드로 작성하면 재현성이 좋고, 수정도 쉽기 때문이다.
코드로 다이어그램을 정의하는 방식이다. 버전 관리가 쉽고, 텍스트 파일이라 협업하기 좋다.
PlantUML에서 C4 Model을 사용하려면 먼저 C4-PlantUML 라이브러리를 포함해야 한다. 기본 구조는 다음과 같다.
@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
' 여기에 다이어그램 코드 작성
@enduml
여기서 중요한 점은 각 C4 레벨마다 다른 include 파일을 사용해야 한다는 것이다.
C4 레벨별 include 파일
' Level 1: System Context Diagram
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
' Level 2: Container Diagram
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
' Level 3: Component Diagram
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml
' Level 4: Code Diagram (일반 PlantUML 클래스 다이어그램 사용, include 불필요)
각 include 파일은 해당 레벨에서 사용할 수 있는 요소들(Person, System, Container, Component 등)을 정의하고 있다. ==상위 레벨의 include를 사용하면 하위 레벨의 요소도 함께 사용==할 수 있다.
예를 들어 C4_Container.puml을 include하면 Context 레벨의 요소(Person, System)도 사용할 수 있고, C4_Component.puml을 include하면 Container와 Context 레벨의 요소도 모두 사용 가능하다.
include 파일의 의미
C4_Context.puml: System Context 다이어그램에 필요한 Person, System, System_Ext 등의 매크로를 정의C4_Container.puml: Container 다이어그램에 필요한 Container, ContainerDb, ContainerQueue 등의 매크로를 정의 (Context 포함)C4_Component.puml: Component 다이어그램에 필요한 Component, ComponentDb 등의 매크로를 정의 (Container, Context 포함)따라서 어떤 레벨의 다이어그램을 그릴 것인지에 따라 적절한 include 파일을 선택하면 된다.
"누가 이 시스템을 사용하고, 어떤 외부 시스템과 연결되어 있는가?"
가장 큰 그림을 그리는 단계다. 시스템 전체를 하나의 박스로 표현하고, 이를 사용하는 사용자(Actor)와 외부 시스템들과의 관계를 표시한다.
Context Diagram에서 사용하는 주요 요소는 다음과 같다.
Person (사용자)

Person(alias, "표시 이름", "설명(선택사항)")
Software System (시스템)

System(alias, "시스템 이름", "설명(선택사항)")
External System (외부 시스템)

System_Ext(alias, "외부 시스템 이름", "설명(선택사항)")
Relationship (관계)

Rel(출발지_alias, 도착지_alias, "관계 설명")
' Ex :
Person(p, "User", "사용자")
System(s, "Main System", "메인 시스템")
Rel(p, s, "관계 설명")
@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
title System Context Diagram - 게임 서버 시스템
Person(player, "플레이어", "게임을 플레이하는 사용자")
Person(admin, "관리자", "시스템을 관리하는 운영자")
System(gameServer, "게임 서버 시스템", "플레이어의 게임 데이터를 관리하고 게임 로직을 처리")
System_Ext(paymentSystem, "결제 시스템", "인앱 결제 처리")
System_Ext(emailService, "이메일 서비스", "회원가입, 비밀번호 찾기 등의 이메일 발송")
System_Ext(pushService, "푸시 알림 서비스", "모바일 푸시 알림 발송")
Rel(player, gameServer, "게임 플레이, 아이템 구매")
Rel(admin, gameServer, "시스템 모니터링, 유저 관리")
Rel(gameServer, paymentSystem, "결제 요청 및 검증", "HTTPS/JSON")
Rel(gameServer, emailService, "이메일 발송 요청", "SMTP")
Rel(gameServer, pushService, "푸시 알림 발송", "REST API")
@enduml

Person(): 사람을 나타낸다. 첫 번째 인자는 내부에서 사용할 별칭(alias), 두 번째는 화면에 표시될 이름이다.System(): 우리가 만들고 있는 시스템을 나타낸다.System_Ext(): 외부 시스템을 나타낸다. 보통 회색으로 표시되어 구분된다.Rel(): 관계를 나타낸다. 화살표 방향은 첫 번째 인자에서 두 번째 인자로 향한다.관계 표현
관계를 표현할 때 더 다양한 옵션을 사용할 수 있다.
System(s1, "System1")
System(s2, "System2")
System(s3, "System3")
System(s4, "System4")
' 양방향 관계
BiRel(s1, s2, "설명")
' 이웃 관계 (옆에 배치)
Rel_Neighbor(s1, s4, "설명")
' 역방향 관계
Rel_Back(s1, s3, "설명")

레이아웃 방향 지정
특정 요소를 기준으로 배치하고 싶을 때 사용한다 단, 관계도에 따라 적용되지 않는 경우도 있다.
System(s1, "System1")
System(s2, "System2")
System(s3, "System3")
System(s4, "System4")
' s1 아래에 s4 배치
Lay_D(s1, s4)
' s3 위에 s2 배치
Lay_U(s3, s2)
' s2 왼쪽에 s1 배치
Lay_L(s2, s1)
' s4 오른쪽에 s3 배치
Lay_R(s4, s3)

"시스템 내부가 어떤 주요 구성 요소들로 이루어져 있는가?"
시스템 내부를 좀 더 들여다보는 단계다. 여기서 Container는 Docker 컨테이너를 의미하는 것이 아니라, "실행 가능한 단위"를 의미한다.
Container Diagram에서는 Context와 다른 include 파일을 사용한다.
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
Container (컨테이너)

Container(alias, "컨테이너 이름", "기술 스택", "설명(선택사항)")
Database (데이터베이스)

ContainerDb(alias, "데이터베이스 이름", "기술", "설명(선택사항)")
Queue (메시지 큐)

ContainerQueue(alias, "큐 이름", "기술", "설명(선택사항)")
@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
skinparam backgroundColor transparent
title Container Diagram - 게임 서버 시스템
Person(player, "플레이어")
System_Boundary(gameServerBoundary, "게임 서버 시스템") {
Container(mobileApp, "모바일 앱", "Unity, C#", "플레이어용 모바일 게임 클라이언트")
Container(GameNetworkServer, "게임 로직 서버", "Photon Fusion", "게임 로직 처리 및 검증")
Container(chatServer, "채팅 서버", "Photon Chat", "실시간 채팅 처리")
ContainerDb(mainDb, "Database", "Firebase", "유저 정보와 \n게임 데이터 저장")
}
Rel(player, mobileApp, "게임 플레이", "Android / iOS")
Rel(mobileApp, GameNetworkServer, "API 호출", "Photon Network")
Rel(mobileApp, chatServer, "API 호출", "Photon Network")
Rel(GameNetworkServer, mainDb, "데이터 읽기/쓰기")
@enduml

System_Boundary(): 시스템의 경계를 표시한다. 중괄호 안에 있는 모든 Container가 이 시스템에 속한다.Container(): 실행 가능한 단위를 나타낸다. 세 번째 인자에 기술 스택을 명시한다.ContainerDb(): 데이터베이스를 나타낸다. 다른 모양의 아이콘으로 표시된다.ContainerQueue(): 메시지 큐를 나타낸다.여러 개의 경계선을 중첩해서 사용할 수도 있다.
System_Boundary(outerBoundary, "전체 시스템") {
System_Boundary(frontendBoundary, "프론트엔드") {
Container(webApp, "웹 앱", "Web")
Container(mobileApp, "모바일 앱", "Unity")
}
System_Boundary(backendBoundary, "백엔드") {
Container(apiServer, "API 서버", "C#")
ContainerDb(database, "데이터베이스", "MySQL")
}
}

"각 Container가 어떤 컴포넌트들로 구성되어 있는가?"
특정 Container를 선택해서, 그 안의 주요 컴포넌트들을 표현하는 단계다. 컴포넌트는 보통 클래스의 그룹이나 모듈 단위를 의미한다.
Component Diagram에서는 또 다른 include 파일을 사용한다.
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml
Component (컴포넌트)

Component(alias, "컴포넌트 이름", "기술/프레임워크", "설명(선택사항)")
ComponentDb (컴포넌트 데이터베이스)

ComponentDb(alias, "데이터 저장소 이름", "기술", "설명(선택사항)")
@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml
title Component Diagram - 턴제 전투 시스템
Container(gameManager, "게임 매니저", "Unity")
Container(uiSystem, "UI 시스템", "Unity UI")
Container(characterController, "캐릭터 컨트롤러", "C#", "플레이어 행동 및 스킬/상태 보유")
Container(enemyController, "적 컨트롤러", "C#", "적 AI 및 스킬/상태 보유")
Container_Boundary(battleSystemBoundary, "턴제 전투 시스템") {
Component(battleManager, "전투 관리자", "C#", "전투 흐름 제어 및 상태 관리")
Component(turnManager, "턴 관리자", "C#", "턴 순서 결정 및 턴 진행")
Component(waveManager, "웨이브 관리자", "C#", "적 웨이브 생성 및 관리")
Component(commandDispatcher, "커맨드 디스패처", "C#", "행동 명령 전달")
Component(rewardManager, "보상 관리자", "C#", "전투 보상 지급")
}
Rel(gameManager, battleManager, "전투 시작/종료")
Rel(battleManager, turnManager, "턴 시작 요청")
Rel(battleManager, waveManager, "웨이브 생성 요청")
Rel(battleManager, rewardManager, "전투 승리 시 보상 지급")
Rel(turnManager, commandDispatcher, "행동 순서 전달")
Rel(waveManager, enemyController, "적 생성 및 배치")
Rel(commandDispatcher, characterController, "플레이어 행동 명령")
Rel(commandDispatcher, enemyController, "적 행동 명령")
Rel(characterController, enemyController, "공격/스킬 실행")
Rel(enemyController, characterController, "공격/스킬 실행")
Rel(battleManager, uiSystem, "전투 UI 업데이트")
Rel(turnManager, uiSystem, "턴 정보 표시")
Rel(commandDispatcher, uiSystem, "행동 연출 요청")
Rel(rewardManager, uiSystem, "보상 화면 표시")
@enduml

Container_Boundary(): Container의 내부를 표시할 때 사용한다.
Component(): Container 내부의 주요 컴포넌트를 나타낸다.ComponentDb(): 데이터 접근 계층을 나타낸다."실제 코드 레벨에서는 어떻게 구현되어 있는가?"
가장 상세한 단계로, 실제 클래스나 인터페이스 수준까지 표현한다. 보통은 UML 클래스 다이어그램을 사용한다. C4-PlantUML에서는 별도의 Code Diagram include가 없고, 일반 PlantUML 클래스 다이어그램 문법을 사용한다.
사실 이 레벨까지 가면 다이어그램보다는 실제 코드를 보는 것이 더 명확할 수 있으므로, C4 Model에서는 Level 4를 생략하는 경우도 있다.
@startuml
title Code Diagram - 게임 캐릭터 시스템
class Character {
+string Name
+int Health
+int MaxHealth
-int _level
+Character(string name)
+TakeDamage(int damage): void
+Heal(int amount): void
+IsAlive(): bool
}
class Player {
+int Experience
+int Gold
+Player(string name)
+GainExperience(int exp): void
+SpendGold(int amount): bool
}
class Enemy {
+int RewardGold
+int RewardExp
-string _aiPattern
+Enemy(string name, int level)
+Attack(): int
}
enum CharacterState {
IDLE
MOVING
ATTACKING
DEAD
}
Character <|-- Player
Character <|-- Enemy
Character --> CharacterState
@enduml

모든 레벨에서 공통으로 사용할 수 있는 유용한 문법들이 있다.
' 배경색 지정
skinparam backgroundColor #EEEEEE
' 배경색 투명
skinparam backgroundColor transparent
' 화살표 색상
skinparam ArrowColor #333333
' 테두리 색상
skinparam ComponentBorderColor #FF6B6B
Person(user, "사용자")
System(database, "데이터베이스")
Rel(user, database, "요청")
' left, right 가능
note right of database
이 시스템은 실시간 데이터베이스 및
유저 분석 통계를 제공함
end note
' 수평방향 지정
left to right direction
' 또는방향 지정
top to bottom direction
' 특정 요소들 사이 간격 조정
Person(user, "사용자")
System(system, "시스템")
user -[hidden]-> system ' 보이지 않는 연결선으로 위치 조정
' 특정 요소에 색상 지정
Person(admin, "관리자", $sprite="person", $tags="admin")
AddElementTag("admin", $bgColor="#FF6B6B", $fontColor="#FFFFFF")
System(criticalSystem, "중요 시스템", $tags="critical")
AddRelTag("critical", $textColor="#FF0000", $lineColor="#FF0000")
상황에 맞게 필요한 레벨만 작성한다. 대부분의 경우 Level 1-2면 충분하고, Level 4는 생략 가능하다.
다이어그램은 의사소통 도구일 뿐이다. 코드가 변경되면 다이어그램도 바로 업데이트하기 어렵다는 점을 인정하자.
팀 내에서 표기법을 통일한다. 색상, 화살표 방향 등의 규칙을 정해두면 좋다.
' 팀 규칙 예시
' - 내부 시스템: 파란색
' - 외부 시스템: 회색
' - 데이터베이스: 녹색
' - 동기 호출: 실선
' - 비동기 호출: 점선
AddElementTag("internal", $bgColor="#4A90E2")
AddElementTag("external", $bgColor="#999999")
AddElementTag("database", $bgColor="#2ECC71")
AddRelTag("sync", $lineStyle="solid")
AddRelTag("async", $lineStyle="dashed")
한 다이어그램에는 5-9개 정도의 요소만 표현하는 것이 이상적이다. 너무 복잡하면 여러 개의 다이어그램으로 나눈다.
XR을 활용한 게임 개발 3기(유니티) 수강생입니다. 곧 수료 하지만 앞으로 이곳에 가끔 저의 개발 경험이 나 지식 기록할까 합니다. 더 나아가 이 사이트가 제 개인위키의 역할을 할 수 있으면 좋겠습니다. 한국 게임 시장을 흔들겠습니다

게임 광고 수익은 단순히 광고를 붙이는 것이 아니라, 여러 광고 네트워크를 경쟁시켜 가장 높은 수익을 만드는 구조입니다.

안녕하세요. 플밍 4기 입니다. 게임 개발을 배우기 전 네트워크 엔지니어 도메인에서 익히고 배웠던 네트워크 이론에 대한 기초 입니다. 학습에 도움이 되길 바라며 공유 드립니다.