Monolithic Architecture
개념
- 모든 도메인을 하나의 프로그램으로 개발
- 컴포넌트 간 메서드(함수) 호출을 통해 상호작용
장점
- 개발과 디버깅, 배포 모두 비교적 수월한 편
- 데이터베이스가 통합되어 데이터 일관성 유지 용이
- 대체로 성능이 뛰어남(컴포넌트끼리 메모리 내의 호출을 통해 상호작용하므로 응답 속도 빠름)
단점
- 배포까지의 사이클(테스트, 컴파일, 빌드 등)이 너무 오래 걸림
- 일부 컴포넌트의 프레임워크, 라이브러리 업데이트가 어려움(다른 컴포넌트에 영향을 주게 됨)
- 일부 컴포넌트의 장애가 전체 서비스로 전파되기 쉬움
- (MSA 방식에 비해) 컴포넌트 간 결합도가 높아 일부 기능 변경의 파급효과가 큼
- 비교적 요구사항 변경에 따른 유지보수가 어려운 편
- 언어, 프레임워크에 종속적
- ex) Spring을 통해 개발 —> 도메인마다 유리한 프레임워크 사용 불가
- 하나의 프로그램에 모든 기능 집약 —> 소스코드가 복잡해짐
MSA(MicroService Architecture)
개념
- 각각의 도메인을 개별 프로그램(서비스)으로 개발
- 프로그램 간 API를 통해 통신하며 상호작용
장점
- 컴포넌트 수정, 개선 빠름(결합도가 낮으므로 개별 컴포넌트 변경의 파급효과가 작음)
- 컴포넌트 간 결합도가 낮아 각자의 관심사에 집중하기 용이한 환경 —> 관심사의 분리
- 서비스 확장 용이 —> 효율적인 리소스 사용
- 개별 장애 전파 최소화
- 프레임워크, 라이브러리의 업데이트가 유연해짐
- 각각의 서비스들을 서로 다른 기술 스택으로 개발 가능
- 다량의 트래픽을 수용해야 하는 기능에 자원을 집중할 수 있음 —> 유지비용 절감
단점
- 운영, 관리가 복잡해짐
- 데이터베이스 분산으로 인해 데이터의 일관성 유지가 어려움
- 비교적 낮은 성능(네트워크 지연에 따른 응답 속도 문제 발생 가능성 높음)
- 통합 테스트가 어려움
- 컴포넌트 간 상호작용하며 발생하는 버그에 취약한 편
MSA로 전환한 대표적인 기업
- 아마존, 넷플릭스, 우버 등
MSA 구조도
REST API(REpresentational State Transfer API)의 개념
- 전송 과정에서 view를 구현하지 않고, 데이터만 전송하는 API
- URI를 통해 리소스(대상)를, HTTP method를 통해 행위를 표현
- HTTP 표준 전송 규약을 따르므로, 다양한 플랫폼에 높은 이식성을 가짐
- HTTP 캐싱을 지원하므로, 클라이언트는 이전의 응답 데이터를 재사용할 수 있음
- 데이터만 전송함으로써 명확한 계층 구조를 이루고 관심사의 분리가 이루어짐
- 클라이언트와 서버 간 결합도를 낮추고 각자의 영역에 집중할 수 있음
- 클라이언트의 상태를 저장하지 않는 무상태성을 가지므로, 항상 완전한 요청이 필요
- 로그인 서비스 등 지속적인 상태 저장이 필요한 영역에 취약한 편
- 별도의 view 없이 데이터만 전달하므로, 데이터만 사용하는 MSA의 서비스 애플리케이션 간 상호작용에 적합
MSA의 서비스 간 통신 방법
REST API 이용
장점
- 단순하고 직관적인 API 제공
- 서버의 구조와 프로세스에 대한 이해 없이도 클라이언트가 서버를 이용할 수 있음
- HTTP를 사용하므로, 이미 구축되어 있는 웹 인프라를 사용할 수 있음
- HTTP 표준 전송 규약을 따르므로, 개발 언어, 플랫폼, 기술 등의 제약을 받지 않고 높은 호환성을 가짐
- 다양한 데이터 포맷 지원(하지만 대체로 JSON을 사용)
- 웹 브라우저와의 호환성 높음 —> 웹 기반 클라이언트에서 이용 시 유리
단점
- 서비스 간 대량의 데이터를 주고받는 경우, HTTP와 JSON 간 오버헤드(작업에 소요되는 추가 자원, 시간)가 커질 수 있음
- 실시간 데이터 전송에 불리
- 타입이 명시적으로 정의되지 않아, 다양한 언어로 개발된 서비스 간 통신에서 특히 취약함
- 타입 불일치나 유효하지 않은 값의 전송을 실행 시간에 발견할 가능성이 높아짐
gRPC(Google Remote Procedure Call) 이용
장점
- 자체 제공 언어인 proto를 통해, 직렬화(포맷 간 변환)를 효율적으로 할 수 있음 —> 성능 향상
- 빠른 응답 속도를 기반으로 실시간 데이터 전송에 유리
- 강력한 타입 검사 기능을 가져, 대부분의 서비스 간 호환 문제를 컴파일 시간에 발견할 수 있음
- 특히 타입 검사 과정에서 취약점을 보이는 동적 타입 언어의 데이터 전송 시 강점을 보임
- 서비스마다 다양한 언어를 사용해도 비교적 안정적
단점
- proto 외의 다른 데이터 포맷을 사용하기 어려움
- 웹 브라우저와의 호환성 낮음
- REST API에 비해 익히기 어려움(proto 언어 학습 필요)
REST API를 통한 서비스 간 통신 예시
Monolithic Architecture
OrderController(내부의 OrderService에서 Menu 엔티티 조회)
@RestController
@RequestMapping("shops/{shopId}/orders")
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@PostMapping()
public ResponseEntity<OrderResponseDto> createOrder
(@PathVariable Long shopId,
@Validated(OnCreate.class) @RequestBody OrderRequestDto orderRequestDto) {
OrderResponseDto orderResponseDto = orderService.takeOrder(orderRequestDto);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(orderResponseDto.getId())
.toUri();
HttpHeaders headers = new HttpHeaders();
headers.setLocation(location);
return ResponseEntity.created(location).headers(headers).body(orderResponseDto);
}
}
MicroService Architecture(단일 메뉴 주문)
application.properties 또는 application.yml(Menu 정보를 받아 올 경로 지정)
menu-service.url=http://menu-service/
클라이언트 요청(JSON)
{
"memberId": 3,
"menuIdAndQuantityMap": {
"2": 4
},
"orderRequest": "맛있게 조리해 주세요.",
"deliveryRequest": "안전하게 배달해 주세요."
}
MenuController(Menu 정보 반환)
@RestController
@RequestMapping("shops/{shopId}/menus")
@RequiredArgsConstructor
public class ShopMenuController {
@GetMapping("{id}")
public ResponseEntity<MenuResponseDto> getMenu(@PathVariable Long id) {
MenuResponseDto menuResponseDto = menuService.getMenu(id);
return ResponseEntity.status(HttpStatus.OK).body(menuResponseDto);
}
}
OrderController(외부의 MenuController에서 Menu 정보 받아 옴)
@RestController
@RequestMapping("shops/{shopId}/orders")
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@Value("${menu-service.url}")
private String menuServiceUrl;
private final WebClient webClient = WebClient.create();
@PostMapping()
public ResponseEntity<OrderResponseDto> createOrder
(@PathVariable Long shopId,
@Validated(OnCreate.class) @RequestBody OrderRequestDto orderRequestDto) {
Long menuId = orderRequestDto.getMenuIdAndQuantityMap()
.keySet().iterator().next();
String url = menuServiceUrl + shopId + "/menus/" + menuId;
MenuResponseDto menuResponseDto = webClient.get()
.uri(url)
.retrieve()
.bodyToMono(MenuResponseDto.class)
.block();
OrderResponseDto orderResponseDto
= orderService.takeOrder(orderRequestDto, menuResponseDto);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(orderResponseDto.getId())
.toUri();
HttpHeaders headers = new HttpHeaders();
headers.setLocation(location);
return ResponseEntity.created(location).headers(headers).body(orderResponseDto);
}
}
Micro Service Architecture(여러 메뉴 주문)
application.properties 또는 application.yml(Menu 정보를 받아 올 경로 지정)
menu-service.url=http://menu-service/
클라이언트 요청(JSON)
{
"memberId": 3,
"menuIdAndQuantityMap": {
"2": 4,
"3": 2,
"5": 1
},
"orderRequest": "맛있게 조리해 주세요.",
"deliveryRequest": "안전하게 배달해 주세요."
}
MenuController(여러 Menu 정보 반환)
@RestController
@RequestMapping("shops/{shopId}/menus")
@RequiredArgsConstructor
public class ShopMenuController {
@GetMapping()
public ResponseEntity<List<MenuResponseDto>> getMenuListToOrder(@RequestParam List<Long> ids) {
List<MenuResponseDto> menuResponseDtoList = menuService.getMenuListToOrder(ids);
return ResponseEntity.status(HttpStatus.OK).body(menuResponseDtoList);
}
}
OrderController(외부의 MenuController에서 여러 Menu 정보 받아 옴)
@RestController
@RequestMapping("shops/{shopId}/orders")
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@Value("${menu-service.url}")
private String menuServiceUrl;
private final WebClient webClient = WebClient.create();
@PostMapping
public ResponseEntity<OrderResponseDto> createOrder
(@PathVariable Long shopId,
@Validated(OnCreate.class) @RequestBody OrderRequestDto orderRequestDto) {
String menuIds = getMenuIdsAsString(orderRequestDto);
List<MenuResponseDto> menuResponseDtoList =
getMenuListFromMenuService(shopId, menuIds);
OrderResponseDto orderResponseDto =
orderService.takeOrder(orderRequestDto, menuResponseDtoList);
return buildResponse(orderResponseDto);
}
private String getMenuIdsAsString(OrderRequestDto orderRequestDto) {
Set<Long> menuIdSet = orderRequestDto.getMenuIdAndQuantityMap().keySet();
return String.join(",", menuIdSet.stream().map(Object::toString)
.collect(Collectors.toList()));
}
private List<MenuResponseDto> getMenuListFromMenuService(Long shopId, String menuIds) {
String url = menuServiceUrl + shopId + "/menus?ids=" + menuIds;
return webClient.get()
.uri(url)
.retrieve()
.bodyToFlux(MenuResponseDto.class)
.collectList()
.block();
}
private ResponseEntity<OrderResponseDto> buildResponse
(OrderResponseDto orderResponseDto) {
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(orderResponseDto.getId())
.toUri();
HttpHeaders headers = new HttpHeaders();
headers.setLocation(location);
return ResponseEntity.created(location).headers(headers).body(orderResponseDto);
}
}
통신 테스트 영상
'Study' 카테고리의 다른 글
개발지식 스터디 발표 자료(빌더 패턴 & 팩토리 메서드 패턴) (0) | 2023.07.18 |
---|---|
객체지향의 사실과 오해 부록A 발표 자료 (2) | 2023.07.12 |
객체지향의 사실과 오해 스터디 7장 발표 자료 (0) | 2023.07.11 |
객체지향의 사실과 오해 스터디 5장 발표 자료 (0) | 2023.07.03 |
커뮤니케이션 스터디 발표 자료(재귀함수) (0) | 2023.06.23 |