사실 이 이야기는 하기가 좀 조심스럽다.
자바와 스프링(스프링부트)에 대해서 거의 모르는 사람이
'스프링부트 돌리는 법이 여러가진데요, 그거에 대해 하나하나 얘기해 보려고요.'라고 하는 격이라.
그래도
1. 내가 궁금하다.
2. 기록하고 싶다.
3. 틀린 부분이 있으면 피드백 받고 싶다.
이 세 가지 욕구가 꽤 크다.
그리하여 내가 정리해 본다.
'스프링부트 돌리는 법'.
아, 내가 갑자기 지금 이 이야기를 하는 이유가 있다.
'스프링부트3 백엔드 개발자 되기' 3장에서 이 이야기를 하고 있기 때문이다.
묘공단? '스부3백개되'? 그게 뭐임? 하는 분은
https://goldenrabbit.co.kr/%eb%ac%98%ea%b3%b5%eb%8b%a8/
여기를 보시면 된다.
자, 다시 본론으로 돌아가자.
'스프링부트3 백엔드 개발자 되기' 3장에서 설명하는 스프링부트 돌리는 법은 다음과 같다.
Controller - Service - Repository (with DAO(Data Access Object)).
여기서
Controller는 HTTP 요청을 받아 처리하고 사용자에게 응답을 전달하는 역할을 한다.
Service는 비즈니스 로직을 담당하며, Controller와 Repository 사이에서 동작한다.
Repository는 데이터베이스와 직접적으로 상호작용하는 책임을 지닌다.
DAO는 Repository와 비슷하지만, 좀 더 세밀한 데이터베이스 연산을 처리한다.
이에 대한 자바 예제 코드는 이 페이지에서는 제공하지 않겠다.
https://github.com/JaeeunSkywalker/jaeeunTheBlackrabbit/tree/7aec1b9d8cef7fed9f15107b38020ece7cb5e4d7
🐇책을 사서 보쎄용~ 책 조아용~~🐇
https://product.kyobobook.co.kr/detail/S000201766024
자, 여기서부터는 잘못된 정보가 전달될 수도 있다.
저를 100% 믿으시면 곤란해지는 영역이다.
그래도 전개해 나가 보겠다.
아키텍처, 패턴에 대해 얘기하자면 정말 정말 범위가 넓어진다.
그래서 나는 웹 서비스를 제작하는 환경에 대해서만 이야기하겠다.
Controller-Service-Repository+DAO 구조는 매우 일반적이고 잘 알려진 패턴 중 하나이다만, 다른 옵션들도 있다.
몇 가지 예를 살펴보자.
Model-View-Controller (MVC) 패턴
이 패턴에서 Controller는 요청을 받고 응답을 생성하는 역할을 하고, Model은 데이터와 로직을 담당하며, View는 사용자에게 보여지는 UI를 처리한다. Service나 Repository는 선택적이다.
Microservices Architecture
마이크로서비스 아키텍처에서는 각 서비스가 독립적으로 동작하며, 각각의 서비스는 자신만의 Controller, Service, Repository 등을 가질 수 있다.
Serverless Architecture
AWS Lambda나 Azure Functions 같은 서버리스 플랫폼을 사용하면, 개별 함수가 Controller처럼 사용될 수 있다. 이러한 경우, Service나 Repository는 선택적이다.
Reactive Programming
Reactive Extensions (예: Spring WebFlux)를 사용하면, 전통적인 Controller-Service-Repository 흐름이 아니라 이벤트 기반으로 처리할 수 있다.
CQRS (Command Query Responsibility Segregation)
이 패턴에서는 시스템을 명령(Command)과 조회(Query)로 나누어 설계한다. 각각의 측면은 독립적인 모델과 로직을 가질 수 있다.
API Gateway Pattern
이 경우에는 하나의 API Gateway가 여러 서비스에 대한 요청을 받아 해당 서비스로 라우팅하는 역할을 한다. 각 서비스는 각자의 Controller, Service, Repository를 가질 수 있다.
이외에도 많은 아키텍처와 패턴이 있고, 필요에 따라 이들을 조합하거나 수정하여 사용할 수 있다.
지금으로선 역량 부족으로 위 예 하나하나에 대해 자세히 설명할 수 없다.
그래도 MVC가 나오니 MVP, MVVM도 생각할 수 있겠는데
MVP는 주로 데스크톱 애플리케이션과 웹에서 사용되고
MVVM은 주로 데스크톱 애플리케이션과 앱(모바일 애플리케이션)에서 사용된다는 정도로
(Controller-Service-Repository+DAO 구조는 주로 웹 서버 측에서 사용됨)
마무리하고 싶다.
여기서 내가 진짜 이야기하고 싶은 건!
스프링부트에서 DB와 소통하는 방법이 여러 개라는 것!
스프링부트 돌리는 법이 여러 개라는 말도 아주 틀린 말은 아니겠지만
내가 정말 말하고자 했던 부분은
'스프링부트3 백엔드 개발자 되기' 책에서는 JPA를 사용하고 있지만 MyBatis도 JDBC도 사용할 수 있다는 것.
'웹소켓만 써도 되는데 왜 이벤트소스를 알아야 하냐'라고 할 수도 있겠지만
최신 기술의 전 단계, 전전 단계 기술을 직접 사용해 보는 것도 즐겁고 의미 있다(라고 외쳐 본다).
Controller-Service-Repository+DAO 구조는
보통 스프링부트와 같은 웹 프레임워크에서 JPA(Java Persistence API)와 같은 ORM(Object-Relational Mapping) 라이브러리를 사용할 때 자주 볼 수 있는 아키텍처 패턴이다.
여기서 잠깐 시작!
※ORM이란?
- Object-Relational Mapping의 약자.
- 이것은 객체지향 프로그래밍 언어와 데이터베이스 관리 시스템(DBMS)간의 호환되지 않는 데이터를 변환하는 프로그래밍 기법이다.
- ORM은 프로그래밍에서 사용하는 객체와 데이터베이스에서 사용하는 테이블을 자동으로 매핑해주는 역할을 한다.
자바에서는 JPA라는 표준이 있고, 이를 사용하면 객체와 데이터베이스 테이블을 쉽게 연결할 수 있다.
@Entity // 이건 JPA가 이 클래스를 데이터베이스 테이블로 사용하게 하라는 표시입니다.
public class Student {
@Id // 이건 'id'가 이 테이블에서의 고유한 값(키)임을 나타냅니다.
private Long id;
private String name;
private int age;
// 여기에는 getter, setter 메서드가 더 있겠죠.
}
여기에서 @Entity와 @Id는 JPA에서 제공하는 어노테이션이다.
이 어노테이션을 사용하면 Student 클래스는 자동으로 데이터베이스의 Student 테이블과 연결된다.
이렇게 하면, 자바 코드 상에서는 Student 객체를 다루기만 하면 되고, 실제로 데이터베이스에는 JPA가 알아서 저장해주거나 가져다 준다.
이게 바로 ORM을 사용하는 이유다.
여기서 잠깐 끝!
전의 내용에 이어서...
Controller: 사용자의 HTTP 요청을 받아서 적절한 서비스 메서드를 호출하고 응답을 반환한다.
Service: 비즈니스 로직을 구현한다. Controller에서 호출된 작업을 수행하고, 필요한 데이터는 Repository를 통해 조회하거나 변경한다.
Repository: JPA와 같은 ORM을 사용하여 데이터베이스와의 CRUD(Create, Read, Update, Delete) 작업을 담당한다.
DAO: 필요한 경우 Repository에서 사용될 수 있으며, 데이터베이스에 직접적인 접근을 담당한다. DAO를 사용하는 것은 선택 사항이며, 복잡한 SQL 쿼리가 필요한 경우에 주로 사용된다.
이러한 구조는 특히 JPA를 사용할 때 잘 맞는다. JPA를 사용하면 Repository 계층을 추상화 할 수 있고, 실제 데이터베이스 작업은 JPA가 대신 처리해 준다. 이는 개발을 빠르게 진행할 수 있도록 도와 준다.
그런데 JPA를 안 쓰고
MyBatis를 사용하면 아키텍처가 어떻게 변할까?
MyBatis를 사용할 때는 JPA를 쓸 때와는 다르게 SQL 쿼리를 직접 작성하게 된다.
MyBatis를 사용해도 기본적인 아키텍처를 Controller-Service-Repository(DAO) 식으로 만들 수 있다. 다만 Repository 대신에 DAO 레이어가 더 명시적으로 존재하게 된다.
코드로 보자.
Controller
@RestController
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping("/students/{id}")
public Student getStudent(@PathVariable int id) {
return studentService.getStudent(id);
}
}
Service
@Service
public class StudentService {
@Autowired
private StudentDAO studentDAO;
public Student getStudent(int id) {
return studentDAO.getStudent(id);
}
}
DAO with MyBatis
@Mapper
public interface StudentDAO {
@Select("SELECT * FROM students WHERE id = #{id}")
Student getStudent(int id);
}
이렇게 하면 MyBatis를 사용하면서도 MVC 패턴과 유사한 구조를 유지할 수 있다.
SQL 쿼리는 DAO 레이어에서 관리되므로, 비즈니스 로직과 데이터 액세스 로직이 명확하게 분리된다.
이로 인해 유지보수가 쉬워지고, 각 레이어의 역할이 명확해진다.
이보다 더 일반적인 형태는 아래와 같다.
(현재 진행 중인 개인 프로젝트에서의 구조가 아래와 유사하다.
(Controller-Service-Repository+DTO <->resources 하단의 *Mapper.xml))
Controller: 사용자의 요청을 처리하고 응답을 반환하는 계층이다.
Service: 비즈니스 로직을 처리하는 계층이다. 트랜잭션 관리도 이곳에서 수행될 수 있다.
Mapper(DAO): MyBatis는 Repository 개념 대신 Mapper라는 개념을 사용한다. Mapper는 SQL 쿼리와 자바 메서드를 매핑하는 인터페이스이다.
SQL Mapping File: 각 Mapper 인터페이스에 대응하는 XML 파일이다. 이 파일에서 실제 SQL 쿼리를 작성한다.
Mapper Interface (DAO)
public interface StudentMapper {
Student selectStudentById(int id);
int insertStudent(Student student);
// ... 다른 CRUD 메서드들
}
SQL Mapping XML File (보통 StudentMapper.xml로 이름 짓는다.)
얘네는 보통 resources 밑에 들어간다.
<mapper namespace="com.example.mapper.StudentMapper">
<select id="selectStudentById" resultType="com.example.model.Student">
SELECT * FROM Student WHERE id = #{id}
</select>
<insert id="insertStudent">
INSERT INTO Student (name, age) VALUES (#{name}, #{age})
</insert>
<!-- ... 다른 SQL 쿼리들 -->
</mapper>
Service Layer
@Service
public class StudentService {
@Autowired
private StudentMapper studentMapper;
public Student getStudentById(int id) {
return studentMapper.selectStudentById(id);
}
public int addStudent(Student student) {
return studentMapper.insertStudent(student);
}
// ... 다른 서비스 메서드들
}
Controller Layer
@RestController
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping("/student/{id}")
public Student getStudent(@PathVariable int id) {
return studentService.getStudentById(id);
}
@PostMapping("/student")
public int createStudent(@RequestBody Student student) {
return studentService.addStudent(student);
}
// ... 다른 컨트롤러 메서드들
}
MyBatis를 사용할 때의 주요 차이점은, Repository 대신 Mapper 인터페이스와 XML 파일을 사용한다는 점이다.
이를 통해 개발자는 SQL 쿼리에 대한 더욱 세밀한 제어를 갖게 된다.
여기까지도 내용이 많아서 JDBC 이하로는 구현하지 않겠다.
근데 마지막으로 이거 하나만 알고 가자.
Entity vs DAO vs DTO
Entity, DAO, DTO는 주로 데이터를 다루는 역할을 하지만, 각각 다른 측면에서 활용된다.
여기서 간단하게 각 용어의 정의와 차이점을 알아 보자.
Entity
역할: 데이터 모델을 표현한다.
특징: 보통 데이터베이스의 테이블과 1:1로 매칭되며, 이를 객체 지향적으로 표현한 것이다.
예시: User, Product, Order 등이 있을 수 있다.@Entity public class User { @Id private Long id; private String name; private String email; // getters and setters }
DAO
역할: 데이터베이스의 CRUD(Create, Read, Update, Delete) 작업을 캡슐화합니다.
특징: 데이터베이스와의 인터페이스를 제공하여, 나중에 데이터베이스 로직이 변경되더라도 상위 레벨의 코드에는 영향을 미치지 않습니다.
예시: UserDao, ProductDao, OrderDao 등이 있을 수 있습니다.public interface UserDao { User getUser(Long id); List<User> getAllUsers(); void saveUser(User user); void deleteUser(Long id); }
DTO (Data Transfer Object)
역할: 계층간 데이터 교환을 위한 객체입니다. 주로 사용자 인터페이스와 비즈니스 로직, 또는 서버와 클라이언트 사이에 데이터를 전송할 목적으로 사용됩니다.
특징: 로직을 포함하지 않은 순수한 데이터 객체로, getter와 setter만을 가질 수 있습니다.
예시: UserDTO, ProductDTO, OrderDTO 등이 있을 수 있습니다.public class UserDTO { private Long id; private String name; private String email; // getters and setters }
요약
Entity: 데이터베이스 테이블을 객체 지향적으로 표현
DAO: 데이터베이스 CRUD 연산을 캡슐화
DTO: 계층 간 데이터 교환을 위한 객체
흑흑 근데 아직도 DAO랑 DTO를 잘 구별해서 못 쓰겠다.
DAO 자리에도 DTO 잘 들어가는데 흑흑.
이 글은 골든래빗 《스프링 부트 3 백엔드 개발자 되기 - 자바 편》의 3장 써머리입니다.