아키텍처
시스템 목적을 달성하기위해 시스템의 상호작용등의 시스템디자인에 대한 제약 및 설계
최적화를 목표로 두고 시스템 구성과 동작원리 그리고 시스템의 구성환경등을 설명 및 설계하는 청사진 또는 설계도
레이어드 아키텍처
Multi-tier 아키텍처 패턴이라고도 하는 레이어드 아키텍처는 코드를 논리적인 부분 혹은 역할에 따라 독립된 모듈로 나누어서 구성하는 패턴
백엔드 API 코드에 가장 널리 적용되는 패턴 중 하나
- Spring, SpringBoot 프로젝트 진행 시, 코드 분리/관리에 대한 방법론
- 애플리케이션 구성 요소들을 수평으로 나눠서 관리함
- 프레젠테이션, 비즈니스, 퍼시스턴스, 데이터베이스로 나눠짐
수평적으로 나누었다는 것은 무엇일까 ?
아래의 그림처럼 레이어로 나눠 놓은 것들을 하나의 클래스, 하나의 메소드 안에 다 구현한다고 해보자.
// AllInOneController.java
package com.sparta.springcore;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@RequiredArgsConstructor // final로 선언된 멤버 변수를 자동으로 생성합니다.
@RestController // JSON으로 데이터를 주고받음을 선언합니다.
public class AllInOneController {
// 등록된 전체 상품 목록 조회
@GetMapping("/api/products")
public List<Product> getProducts() throws SQLException {
ArrayList<Product> products = new ArrayList<>();
// DB 연결
Connection connection = DriverManager.getConnection("jdbc:h2:mem:springcoredb", "sa", "");
// DB Query 작성 및 실행
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("select * from product");
// DB Query 결과를 상품 객체 리스트로 변환
while (rs.next()) {
Product product = new Product();
product.setId(rs.getLong("id"));
product.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime());
product.setModifiedAt(rs.getTimestamp("modified_at").toLocalDateTime());
product.setImage(rs.getString("image"));
product.setLink(rs.getString("link"));
product.setLprice(rs.getInt("lprice"));
product.setMyprice(rs.getInt("myprice"));
product.setTitle(rs.getString("title"));
products.add(product);
}
// DB 연결 해제
rs.close();
connection.close();
// 응답 보내기
return products;
}
// 신규 상품 등록
@PostMapping("/api/products")
public Product createProduct(@RequestBody ProductRequestDto requestDto) throws SQLException {
// 요청받은 DTO 로 DB에 저장할 객체 만들기
Product product = new Product(requestDto);
LocalDateTime now = LocalDateTime.now();
product.setCreatedAt(now);
product.setModifiedAt(now);
// DB 연결
Connection connection = DriverManager.getConnection("jdbc:h2:mem:springcoredb", "sa", "");
// DB Query 작성
PreparedStatement ps = connection.prepareStatement("select max(id) as id from product");
ResultSet rs = ps.executeQuery();
if (rs.next()) {
// product id 설정 = product 테이블의 마지막 id + 1
product.setId(rs.getLong("id") + 1);
} else {
throw new SQLException("product 테이블의 마지막 id 값을 찾아오지 못했습니다.");
}
ps = connection.prepareStatement("insert into product(id, title, image, link, lprice, myprice, created_at, modified_at) values(?, ?, ?, ?, ?, ?, ?, ?)");
ps.setLong(1, product.getId());
ps.setString(2, product.getTitle());
ps.setString(3, product.getImage());
ps.setString(4, product.getLink());
ps.setInt(5, product.getLprice());
ps.setInt(6, product.getMyprice());
ps.setString(7, product.getCreatedAt().toString());
ps.setString(8, product.getModifiedAt().toString());
// DB Query 실행
ps.executeUpdate();
// DB 연결 해제
ps.close();
connection.close();
// 응답 보내기
return product;
}
// 설정 가격 변경
@PutMapping("/api/products/{id}")
public Long updateProduct(@PathVariable Long id, @RequestBody ProductMypriceRequestDto requestDto) throws SQLException {
Product product = new Product();
// DB 연결
Connection connection = DriverManager.getConnection("jdbc:h2:mem:springcoredb", "sa", "");
// DB Query 작성
PreparedStatement ps = connection.prepareStatement("select * from product where id = ?");
ps.setLong(1, id);
// DB Query 실행
ResultSet rs = ps.executeQuery();
if (rs.next()) {
product.setId(rs.getLong("id"));
product.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime());
product.setModifiedAt(rs.getTimestamp("modified_at").toLocalDateTime());
product.setImage(rs.getString("image"));
product.setLink(rs.getString("link"));
product.setLprice(rs.getInt("lprice"));
product.setMyprice(rs.getInt("myprice"));
product.setTitle(rs.getString("title"));
} else {
throw new NullPointerException("해당 아이디가 존재하지 않습니다.");
}
// DB Query 작성
ps = connection.prepareStatement("update product set myprice = ?, modified_at = ? where id = ?");
ps.setInt(1, requestDto.getMyprice());
ps.setString(2, LocalDateTime.now().toString());
ps.setLong(3, product.getId());
// DB Query 실행
ps.executeUpdate();
// DB 연결 해제
rs.close();
ps.close();
connection.close();
// 응답 보내기 (업데이트된 상품 id)
return product.getId();
}
}
controller, service, model 등 모두 한 곳에 구현되어도 서버는 동작한다.
하지만 이런 코드는 가독성도 떨어질 뿐 아니라 코드 중의 어떤 값을 수정해야 할 때에도 힘들어 진다.
객체지향 프로그래밍에 SOLID라는 원칙이 있다.
이 중 S는 단일 책임 원칙(Single responsibility principle), 하나의 파일이 너무 많은 역할을 맡고 있기 때문에
에러를 핸들링 하거나 수정하기가 상당히 힘들어 진다.
위의 코드에서 컨트롤러 역학을 하는 코드만 분리
@RestController // JSON으로 데이터를 주고받음을 선언합니다.
public class ProductController {
// 등록된 전체 상품 목록 조회
@GetMapping("/api/products")
public List<Product> getProducts() throws SQLException {
ProductService productService = new ProductService();
List<Product> products = productService.getProducts();
// 응답 보내기
return products;
}
// 신규 상품 등록
@PostMapping("/api/products")
public Product createProduct(@RequestBody ProductRequestDto requestDto) throws SQLException {
ProductService productService = new ProductService();
Product product = productService.createProduct(requestDto);
// 응답 보내기
return product;
}
// 설정 가격 변경
@PutMapping("/api/products/{id}")
public Long updateProduct(@PathVariable Long id, @RequestBody ProductMypriceRequestDto requestDto) throws SQLException {
ProductService productService = new ProductService();
Product product = productService.updateProduct(id, requestDto);
return product.getId();
}
}
서비스를 맡은 부분을 분리
public class ProductService {
public List<Product> getProducts() throws SQLException {
ProductRepository productRepository = new ProductRepository();
return productRepository.getProducts();
}
public Product createProduct(ProductRequestDto requestDto) throws SQLException {
ProductRepository productRepository = new ProductRepository();
// 요청받은 DTO 로 DB에 저장할 객체 만들기
Product product = new Product(requestDto);
productRepository.createProduct(product);
return product;
}
public Product updateProduct(Long id, ProductMypriceRequestDto requestDto) throws SQLException {
ProductRepository productRepository = new ProductRepository();
Product product = productRepository.getProduct(id);
if (product == null) {
throw new NullPointerException("해당 아이디가 존재하지 않습니다.");
}
int myPrice = requestDto.getMyprice();
productRepository.updateProductMyPrice(id, myPrice);
return product;
}
}
보통 애플리케이션의 클래스는 두 종류로 나뉜다.
하나는 기능을 수행하는 클래스, 하나는 데이터를 담는 클래스
기능을 맡은 클래스는 컨트롤러/서비스/퍼시스턴스 처럼 로직을 수행
데이터를 담는 클래스는 말 그대로 데이터만 담는다.
기능을 맡은 클래스에게 데이터를 요청했을 때, 데이터를 담는 클래스에 요청을 보내 응답을 받아온다.
그 결과는 아직까지 데이터 객체 그 자체이다. 아무런 기능도 없다.
이런 비즈니스 데이터를 담기 위한 클래스들이 있는데 컨트롤러 - Dto / 서비스 - 모델 / 퍼시스턴스 - Entity 이다.
이해하기 쉬운 블로그
'Java > Spring' 카테고리의 다른 글
[Spring] 제어의 역전 IoC(Inversion of Control)와 bean (0) | 2022.12.04 |
---|---|
[Spring] 의존관계 주입(Dependency Injection), 의존성 주입, DI (0) | 2022.12.04 |
[Spring]Controller, Repository, Service (0) | 2022.11.29 |
[Spring]REST API (0) | 2022.11.27 |
[Spring] 디자인 패턴 (0) | 2022.11.27 |
댓글