1. MVC 패턴이란?
: 소프트웨어 공학에서 사용되는 소프트웨어 디자인 패턴의 한 종류를 말하는데, Model-View-Controller의 약자이다.
이 패턴을 사용하는 궁극적인 목적은, 사용자 인터페이스(Interface)로부터 비즈니스 로직(Logic)을 분리함으로써, 애플리케이션의 시각적 요소와 그 이면에서 실행되는 비즈니스 로직 간에 영향을 최소한으로 줄여, 프로그램의 유지 보수성을 높이고, 성능을 향상시키는 데에 있다.
2. MVC 패턴의 구성 요소와 원칙
- 모델(Model)
: 데이터와 관련된 책임을 담당하는 레이어
- [POJO] 비즈니스 로직을 수행한다.
- 주로 상태 변화를 처리한다. (최근에는 Entity, VO, Aggregate로 나누어서 관리한다. -> 도메인 주도 설계)
- 데이터와 그 데이터를 통한 행동을 갖는 객체이다.
원칙)
"Model은 Controller와 View에 의존해서는 안된다!"
☞ Model이 Controller, View의 책임을 가지면 안 된다는 것이다. 각 레이어에서 담당하는 책임이라는 것이 존재한다.
☞ 오로지 '데이터'에 대한 '순수 로직'만 존재하는 것이 바람직하다. - 뷰(View)
: 사용자에게 보여질 사용자 인터페이스(UI)를 담당하는 레이어
- 웹에서는 웹 브라우저로 렌더링 되는 페이지들을 View의 영역으로 말한다.
- 모델이 처리한 데이터를 받아서 합산하고 사용자(Client)의 브라우저로 렌더링 되는 페이지이다.
- 동적으로 처리되어야 할 데이터를 시각화 해주는 역할도 하는데, 이때 상황과 도메인에 따라 달라지는 값을 가져와야 하는 경우에만 Model에서 받아온다.
원칙)
"데이터 및 로직과 관련된 코드가 없어야 한다!"
☞ 오로지 UI에만 관여하는 레이어이므로, 당연하다.
"View는 Model에만 의존해야 한다!"
☞ 여기서 말하는 '의존'은 코드상의 의존이 아닌, 순수 도메인 데이터에 대한 의존을 말한다.
즉, View에 사용되는 값은 오로지 Model을 통해서 전달된 값이어야만 한다는 것이다. - 컨트롤러(Controller)
: Model과 View를 연결해주는 레이어
- 일종의 중개자로서, 사용자의 요청에 맞는 서비스를 실행하는데, 이때 실질적인 로직의 처리는 Model에서 진행하는 것이다.
- Model 에서 처리한 값을 View에 전달하여 반환하는 역할도 한다.
- 사용자의 요청(웹 브라우저를 통해 들어오는 요청)을 가장 먼저 마주하는 레이어이다.(URL을 요청 받으면, 그에 맞는 화면을 띄워주므로, View 보다도 먼저 마주한다고 볼 수 있다.)
원칙)
"Controller는 Model과 View에 의존해야 한다!"
☞ 여기서 말하는 '의존'은, 중개자 역할로서의 의존을 말한다. 즉, Controller 혼자 데이터(Model 영역)에 대한 처리를 단독으로 하면 안된다는 말이다.
3. Spring 프레임워크에서의 레이어드 아키텍처(Layered Architecture)
앞서 우리는 MVC 패턴이 무엇인지에 대해 배웠는데, 실무에서는 단순히 Model - View - Controller 인 이 3가지로 구성하다 보면, 여러 문제가 발생할 수 있다. 큰 그림에서는 Model - View - Controller로 구분함으로써, 비즈니스 로직 부분과 UI를 철저하게 구분한다는 원칙에는 변함이 없지만, 백엔드 엔지니어가 실무 프로젝트를 다룬다면, Controller 부분과 Model 부분에서 보통 더 세분화하여 개발하곤 한다. 이 포스팅에서는 그러한 세분화된 패턴인 레이어드 아키텍처에 대해 알아보기로 하자!
- Presentation Layer
- 사용자와 가장 가까운 계층으로, 사용자와의 상호작용을 담당.
- Spring 에서의 MVC 패턴이 가장 도드라지게 나타나는 영역이며, 여기에 프론트-컨트롤러(DispatcherServlet), 컨트롤러, 뷰, 모델이 포함된다. - Business Layer (Service Layer)
- 실제 비즈니스 로직을 수행하는 컴포넌트로, 트랜잭션 및 실질적인 비즈니스 로직 수행 담당.
- 컨트롤러(Presentation Layer)에서 요청을 보내면, *DAO(Data Access Layer) 를 이용하여 실제로 비즈니스 로직을 수행하게 된다.
- 보통 하나의 비즈니스 로직은 하나의 트랜잭션(ACID 특징)으로 동작한다는 원칙이 있다. - Data Access Layer(Repository Layer)
- 데이터베이스에 연동되어 데이터의 저장과 조회를 담당하는 영역.
- DB에 값을 저장하거나 가져오기 위해, JDBC, MyBatis, JPA 등을 사용하여 구현한 DAO 영역이다.
※ 실제 프로젝트에서의 패키지 구성
4. Domain Object / Entity / VO / DTO / DAO 의 개념
- Domain Object
: 말 그대로 도메인 객체를 말하는데, 여기서 '도메인(Domain)'이란, 일반적인 요구사항, 전문 용어, 그리고 컴퓨터 프로그래밍 분야에서 문제를 해결하기 위해 설계된 어떤 소프트웨어 프로그램에 대한 기능성을 정의하는 연구의 한 영역.
즉, 애플리케이션의 기능, (기획의) 요구사항을 개발하는 영역을 도메인이라고 정의하고 있다.
예를 들어, 온라인 서점 사이트에서 책을 조회하고 구매한다고 가정할 때, 온라인 서점 소프트웨어는 상품의 조회, 구매, 결제 등의 기능을 제공해야 한다. 이때 '온라인 서점'은 소프트웨어로 해결하고자 하는 문제 영역, 즉 도메인에 해당된다.
이러한 도메인은 다시 하위 도메인으로 나눌 수 있으며, '온라인 서점'을 다시 예로 든다면, 하위 도메인으로는 '주문', '결제', '배송' 과 같이 나눌 수 있을 것이다.
JPA 에서는 Entity와 Domain을 용어적인 측면에서 혼용하여 사용하기도 한다. - Entity
: 엔티티의 가장 큰 특징은 식별자를 가진다는 것이다. 즉, 식별자 외의 데이터가 변경된다고 해서, 그 식별자를 가진 객체가 다른 객체가 되지는 않는다는 것이다. 예를 들어, Purchase 라는 도메인이 주문id를 식별자로 가진다고 하자. 이때 Purchase의 주문 상태 데이터를 변경한다고 해서, 해당 주문이 다른 주문으로 되지는 않는다는 말이다.
또한 데이터베이스의 '테이블'과 혼동하면 곤란하다. 엔티티는 SQL 상에 실제로 존재하는 것이 아닌, 일종의 개념이다. 반면 데이터베이스의 테이블은 DB 또는 SQL 상에 실제로 존재하며, 물리적인 구조를 가지고 있다. 따라서 엔티티는 논리 모델에서 사용되고, 테이블은 물리 모델에서 사용되는 용어이다.
정리하면, Entity는 실체, 객체라는 의미를 담고 있으며, 업무에 필요하고 유용한 정보를 저장 및 관리하기 위한 집합적인 개념이라고 말할 수 있다.
Spring에서 사용되는 Entity라는 용어는, 주로 ORM 혹은 ODM 등과 같이 DB의 Row 또는 Document를 객체에 매핑시켜주는 기술에서 사용된다. 즉, 이때의 Entity는 DB 의 데이터와 맵핑되는 객체를 말하며, 식별자가 존재하는 특성 덕분에 Setter를 이용하여 '가변 객체'로서 사용할 수 있다. - VO(Value Object)
: 값 그 자체를 표현하는 객체로 사용된다. VO는 Entity와 달리, 식별자를 가지지 않는다. 즉, VO 객체 내의 단 하나의 데이터라도 변경되면, 그 객체는 서로 다른 객체가 되어 버린다. 이 때문에 같은 객체임을 확실하게 보장하기 위해, VO는 일반적으로 '불변 객체'로서 사용한다. 이 말은 Setter 를 구현하지 않는다는 말이기도 하다. 또한 서로 다른 인스턴스이더라도 값이 같으면 같은 객체가 되기 때문에, 서로 같은 객체인지를 판단할 때, equals 또는 hashCode 를 구현하여 판별한다.
또한 Spring의 MyBatis 를 사용할 때 주로 VO 라는 개념을 사용한다. 그 이유로는, MyBatis는 *SQL-Mapper 이기 때문에, 어떠한 로직에 의해 가져온 데이터는 그저 맵핑되는 값을 가져온 것일 뿐, 실제 DB의 테이블과 매핑되는 것은 아니기 때문이다.
★ MyBatis에서는 VO! / JPA에서는 Entity!
Spring에서 DB와의 연동 기술을 적용할 때, MyBatis(SQL-Mapper)를 사용하는 경우라면, VO 라고 표현 및 사용하고,
JPA(ORM 기술)를 사용하는 경우라면, Entity 라고 표현 및 사용한다.
SQL-Mapper 는 RDBMS를 객체 지향 모델로 매핑하는 걸 도와주는 인터페이스(?)로 보면 된다. 즉, 객체를 SQL statement에 매핑시킬 수 있도록 도와주는 역할만 하므로, SQL문을 명시해줘야 한다. 또한, 개발자가 작성한 SQL 문으로 해당되는 Row를 읽어온 후, 결과 값을 오브젝트화시켜 사용가능하도록 만들어준다.
ORM(Object Relational Mapping)은 데이터베이스를 객체로 매핑함으로써, 객체 간의 관계를 바탕으로 테이블을 만들고 관리하는 것을 말한다. 즉 SQL-Mapper처럼 단순 인터페이스 역할이 아닌, 객체가 DB에 직접적으로 매핑된다는 특징을 가지고 있어, RDBMS 상에서의 '관계' 까지 Object에 반영되며, 이로 인해 SQL문을 명시하지 않고, 정해진 직관적인 코드(메서드)로 데이터를 조작할 수 있게 해준다. - DTO(Data Transfer Object)
: 데이터 교환을 위한 객체이다. DB와 연동되어 있는 VO 또는 Entity에서의 데이터 조작을 피하기 위해, DTO를 로직의 파라미터 및 리턴으로 사용한다. 데이터 교환만을 위해 사용되므로, DTO가 직접적인 로직을 갖지는 않으며, Getter와 Setter 만을 가진다. - DAO(Data Access Object)
: 데이터베이스의 Data에 접근하기 위한 객체이다. 프로젝트의 서비스 레이어(실제 비즈니스 로직이 돌아가는 곳)와 실제 데이터베이스를 연결하는 역할을 하며, 특히 JPA에서는 DB에 데이터를 CRUD 하는 'Repository' 객체들이 DAO에 해당된다.
(정확하게 말하면 'Repository'는 원래 Entity 객체를 보관하고 관리하는 '저장소'를 말하며, 메모리에 로드된 객체 컬렉션에 대한 집합 처리를 위한 인터페이스 제공을 위한 역할이다.
'DAO'는 퍼시스턴스 로직인 Entity Bean을 대체하기 위해 만들어진 개념으로, DB의 CRUD 쿼리와 1:1 매칭되는 세밀한 단위의 오퍼레이션을 제공한다.
JPA를 활용한 개발에서는, 데이터에 접근하도록 하는 DB접근 관련 로직(여러 오퍼레이션)을 모아둔 객체인 DAO의 기능이 하나의 Repository 안에 포함시켜 개발하는 경우가 많으므로, 용어적인 측면에서 DAO와 Repository를 동일한 의미로 사용하기도 한다.)
/* MemberService 코드 */
import {Group}.{Artifact}.domain.Member;
import {Group}.{Artifact}.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
//회원 가입
@Transactional
public Long join(Member member) {
validateDuplicateMember(member); // 중복 회원 검증 로직
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
//EXCEPTION
List<Member> findMembers = memberRepository.findByEmpID(member.getEmp_ID());
if (!findMembers.isEmpty()) {
throw new IllegalStateException("이미 존재하는 회원입니다.");
}
}
//회원 전체 조회
public List<Member> findMembers() {
return memberRepository.findAll();
}
//회원 단일 조회
public Member findOne(Long memberId) {
return memberRepository.findOne(memberId);
}
@Transactional
public void update(Long id, String mem_Name) {
Member member = memberRepository.findOne(id);
member.setMem_Name(mem_Name);
}
}
/* Member 엔티티 */
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "pri_member_id")
private Long id;
//권한 부여 위한 그룹 넘버링
private Long GROUP_NO;
//회원 사번
private Long emp_ID;
//회원 이름
private String mem_Name;
//회원 직급
private String level;
//회원 로그인 아이디
private String mem_ID;
//회원 패스워드
private String mem_PWD;
}
/* MemberRepository 코드 */
import {Group}.{Artifact}.domain.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class MemberRepository {
private final EntityManager em;
public void save(Member member) {
em.persist(member);
}
public Member findOne(Long id) {
return em.find(Member.class, id);
}
public List<Member> findAll() {
return em.createQuery("SELECT m from Member m", Member.class).getResultList();
}
public List<Member> findByEmpID(Long emp_Id) {
return em.createQuery("SELECT m from Member m WHERE m.emp_ID = :emp_ID", Member.class)
.setParameter("emp_ID", emp_Id)
.getResultList();
}
// public Member findByEmpID(Long emp_id) {
// return em.find(Member.class, emp_id);
// }
}
참고
https://m.blog.naver.com/jysaa5/221751719334
https://www.inflearn.com/questions/111159
https://www.inflearn.com/questions/16046
https://velog.io/@gidskql6671/DTO-VO-Entity
'Java' 카테고리의 다른 글
[Java] Java의 메모리 영역(Runtime Data Area)과 OOM 종류 (0) | 2024.03.20 |
---|---|
[Java] JVM 실행 옵션 (3) - Advanced Runtime Options (0) | 2022.11.28 |
[Java] Spring Boot에서 H2 데이터베이스 설치 및 테스트 작동 확인하기 (0) | 2022.10.24 |
[Java] JVM 실행 옵션 (2) - Non-Standard Options (1) | 2022.10.04 |
[Java] JVM 실행 옵션 (1) - Standard Options (1) | 2022.09.30 |
최근댓글