Skip to content

Commit aa37c47

Browse files
committed
0812포스팅추가
1 parent 506f837 commit aa37c47

9 files changed

Lines changed: 216 additions & 0 deletions

_posts/2025-08-12- Armeria(1).md

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
---
2+
layout: post
3+
title: Armeria의 DocService구조 이해
4+
date: 2025-08-12
5+
categories: [OOP,Jackson,OpenSource]
6+
tags: [OpenSource]
7+
---
8+
9+
### 배경
10+
11+
[Armeria](https://github.com/line/armeria)라는 프로젝트에 기여하기위해 이슈를 찾아보던 중, 다음과 같은 이슈를 발견했다.
12+
13+
[#6313이슈](https://github.com/line/armeria/issues/6313)
14+
15+
16+
17+
<p style="text-align: center;">
18+
<img src="../assets/img/0812/armeria_6313이슈.png" alt="이미지 없음" width="1000" height="300" />
19+
</p>
20+
21+
어떤 내용일까 싶어서 찾아보니 DocService라는 문서생성 총괄 클래스에 상속과 다형성을 추가해달라는 의미로 이해하여 기여를 해보기로 했다.
22+
23+
24+
<p style="text-align: center;">
25+
<img src="../assets/img/0812/6313질문답변.png" alt="이미지 없음" width="1000" height="400" />
26+
</p>
27+
28+
29+
30+
31+
---
32+
### Armeria의 특징
33+
34+
Armeria는 **어떠한 상황** 에서도 사용가능하도록 REST, gRPC, thrift와 같은 네트워크 프로토콜과 spring, dropwizard라는 프레임워크, java, kotlin, scala와 같은 프로그래밍 언어 등등 상살할 수 있는 거의 모든 기술을 통합한 프레임워크다. Armeria를 사용하여 간단한 blogService application을 만들때, 다음과 같이 시작할 수 있다.
35+
36+
``` java
37+
public final class Main {
38+
39+
private static final Logger logger = LoggerFactory.getLogger(Main.class);
40+
41+
public static void main(String[] args) throws Exception {
42+
final Server server = newServer(8080);
43+
44+
server.closeOnJvmShutdown();
45+
46+
server.start().join();
47+
48+
logger.info("Server has been started. Serving DocService at http://127.0.0.1:{}/docs",
49+
server.activeLocalPort());
50+
}
51+
52+
static Server newServer(int port) {
53+
final ServerBuilder sb = Server.builder();
54+
final DocService docService = //서버에 붙이는 DocService도 빌더패턴으로 붙여 제작
55+
DocService.builder()
56+
.exampleRequests(BlogService.class,
57+
"createBlogPost",
58+
"{\"title\":\"My first blog\", \"content\":\"Hello Armersssia!\"}")
59+
.build();
60+
return sb.http(port) //서버에 서비스를 빌더패턴으로 붙여서 제작.
61+
.annotatedService(new BlogService())
62+
.serviceUnder("/docs", docService)
63+
.build();
64+
}
65+
}
66+
67+
```
68+
69+
여기서 사용자는 BlogService와 같은 본인의 도메인이 담긴 서비스를 제작하고 **ServerBuilder** 를 통해 메소드 체이닝을 하면서 간단하게 서비스를 붙이고, DocService와 같은 부가기능을 추가해나갈 수 있다.
70+
71+
72+
### DocService란?
73+
74+
내가 기여하고자 하는 이 DocService는 사용자가 제작한 서비스의 네트워크 프로토콜(REST, gRPC, thrift)을 가리지 않고, Swagger-ui처럼 자동으로 문서화를 해주는 클래스다. REST의 예시 화면은 다음과 같다.
75+
76+
77+
78+
79+
<p style="text-align: center;">
80+
<img src="../assets/img/0812/RestDocs.png" alt="이미지 없음" width="600" height="800" />
81+
</p>
82+
83+
<p style="text-align: center;">
84+
<img src="../assets/img/0812/RestDocs2.png" alt="이미지 없음" width="700" height="600" />
85+
</p>
86+
87+
어느정도 각 API엔드포인트의 경로와 객체 속성정보를 보여주지만, 상속과 다형성에 관련된 정보가 없어서 이번에 기여해보고자 한다.
88+
89+
#### DocService 코드
90+
이 DocService의 클래스다이어그램을 통해 주변 class, interface를 살펴보면 원리를 이해하기 쉽다.
91+
92+
93+
<p style="text-align: center;">
94+
<img src="../assets/img/0812/simple_DocService.png" alt="이미지 없음" width="600" height="400" />
95+
</p>
96+
97+
<p style="text-align: center;">
98+
<img src="../assets/img/0812/simple_DocServiceBuilder.png" alt="이미지 없음" width="700" height="500" />
99+
</p>
100+
101+
DocService라는 객체는 사용자가 개발한 API서버의 정보를 받아 문서화를 진행하는 대표 클래스이다. DocService 는 **Builder패턴** 으로 구성될 수 있는데, 이때 DocServiceBuilder가 DescriptiveTypeInfoProvider의 구현체를 입력받아 사용할 수 있다.
102+
103+
``` java
104+
// DocServiceBuilder.java
105+
106+
/**
107+
* Adds the specified {@link DescriptiveTypeInfoProvider}s used to create a {@link DescriptiveTypeInfo} from
108+
* a type descriptor.
109+
*/
110+
@UnstableApi
111+
public DocServiceBuilder descriptiveTypeInfoProvider(
112+
DescriptiveTypeInfoProvider descriptiveTypeInfoProvider) {
113+
requireNonNull(descriptiveTypeInfoProvider, "descriptiveTypeInfoProvider");
114+
if (this.descriptiveTypeInfoProvider == null) {
115+
this.descriptiveTypeInfoProvider = descriptiveTypeInfoProvider;
116+
} else {
117+
this.descriptiveTypeInfoProvider.orElse(descriptiveTypeInfoProvider);
118+
}
119+
return this;
120+
}
121+
122+
//....
123+
```
124+
125+
126+
```java
127+
//DocService.java
128+
129+
public final class DocService extends SimpleDecoratingHttpService {
130+
//...
131+
static final List<DocServicePlugin> plugins = ImmutableList.copyOf(ServiceLoader.load(
132+
DocServicePlugin.class, DocService.class.getClassLoader()));
133+
134+
static final List<DescriptiveTypeInfoProvider> SPI_DESCRIPTIVE_TYPE_INFO_PROVIDERS =
135+
ImmutableList.copyOf(ServiceLoader.load(
136+
DescriptiveTypeInfoProvider.class, DocService.class.getClassLoader()));
137+
//...
138+
}
139+
140+
```
141+
DocService는 Java의 SPI를 이용해 DocServicePlugin과 DescriptiveTypeInfoProvider의 구현체를 읽어오는데, 상당히 흥미로운 기술이라 다음에 기회가 된다면 포스팅할 계획이다.
142+
143+
한편, DescriptiveTypeInfoProvider의 경우, DocServiceBuilder를 통해 입력받을 수도 있고, SPI를 통해 자동으로 가져올 수도 있는데, 이에 대한 우선순위 설정이 존재한다.
144+
145+
146+
```java
147+
//DocService.java
148+
149+
public final class DocService extends SimpleDecoratingHttpService {
150+
//...
151+
static final class SpecificationLoader {
152+
//...
153+
private static DescriptiveTypeInfoProvider composeDescriptiveTypeInfoProvider(
154+
@Nullable DescriptiveTypeInfoProvider descriptiveTypeInfoProvider) {
155+
156+
return typeDescriptor -> {
157+
if (descriptiveTypeInfoProvider != null) {
158+
// Respect user-defined provider first.
159+
final DescriptiveTypeInfo descriptiveTypeInfo =
160+
descriptiveTypeInfoProvider.newDescriptiveTypeInfo(typeDescriptor);
161+
if (descriptiveTypeInfo != null) {
162+
return descriptiveTypeInfo;
163+
}
164+
} //사용자가 Builder로 주입한 Provider를 우선 사용한다. 만약 null이 나온다면..
165+
166+
for (DescriptiveTypeInfoProvider provider : SPI_DESCRIPTIVE_TYPE_INFO_PROVIDERS) {
167+
final DescriptiveTypeInfo descriptiveTypeInfo =
168+
provider.newDescriptiveTypeInfo(typeDescriptor);
169+
if (descriptiveTypeInfo != null) {
170+
return descriptiveTypeInfo;
171+
}
172+
} //사용자 설정 Provider가 없다면 SPI로 가져온 Provider를 사용한다.
173+
return null;
174+
};
175+
}
176+
}
177+
}
178+
}
179+
```
180+
181+
182+
183+
---
184+
185+
DocService는 **문서 작성** 이라는 작업을 총괄하고, 각 구현체에게 명령하는 클래스이다. 그래서 실질적으로 어떤 코드를 무엇을 기준으로 읽어오고 어떻게 구성할지에 대한 실질적이고 구체적인 로직을 갖고있지 않다. (=인터페이스를 참조하여 객체지향적으로 개발되어있다.)
186+
187+
DocService에게 명령을 받아 구체적인 작업을 진행하는 인터페이스는 다음과 같다.
188+
189+
1. **DocServicePlugin** / interface
190+
-
191+
- 특정 기능을 제공하는 전문 기능, 예를들어, gRPCPlugin이라면 DocService는 gRPC가 뭔지 몰라도 이 gRPCSerivcePlugin을 불러와서 일을 시킨다.
192+
193+
194+
2. **DescriptiveTypeInfoProvider** / interface
195+
-
196+
-특정 데이터타입을 어떻게 문서로 표현할지 상세한 정보를 제공하는(provide) 인터페이스, --Plugin들이 큰 구조를 잡다가 특이한 객체같은걸 만나면 이 Provider에게 물어본다.
197+
198+
199+
3. **DocServiceFilter** / interface
200+
-
201+
- 서비스를 생설할 때 어떤 메소드를 문서에 포함시키고, 제외시킬지 고르는 말 그대로의 필터 역할.
202+
203+
4. **ServiceSpecification** / class
204+
-
205+
- --Plugin, --Provider, --filter들이 작업을 마치고 생성하는 최종 문서화 산물. DocService는 이 상세 명세를 받고 UI가 읽을 수 있는 Json으로 변환함.
206+
207+
<br>
208+
<br>
209+
210+
---
211+
212+
### 앞으로의 계획
213+
214+
앞으로 Jackson의 애노테이션을 가지고 상속과 다형성 기능을 구현해야 하는데, --Provider를 중심으로 작업을 생각중이다.
215+
216+
79.1 KB
Loading

assets/img/0812/RestDocs.png

54 KB
Loading

assets/img/0812/RestDocs2.png

35.8 KB
Loading
113 KB
Loading

assets/img/0812/grpcDocs.png

112 KB
Loading
23.7 KB
Loading
37.4 KB
Loading

assets/img/0812/thriftDocs.png

103 KB
Loading

0 commit comments

Comments
 (0)