MVC 패턴에 대해 정리한다.
백엔드 개발을 공부하다보니 MVC 패턴의 세 가지 말고도 다른 개념들이 많아서 정리해보고자 마음먹었다.
늘 그렇지만 왜 나오게 되었는지, 어떤 목적으로 사용되는지를 기준으로 파악해야 한다.
한문장 요약: MVC란 소프트웨어 개발 요소를 이해하기 쉽게 분리하는 디자인 패턴이다.
MVC 패턴은 소프트웨어 개발에 사용되는 디자인 패턴으로, 소프트웨어의 비즈니스 로직과 화면을 분리하는 데 중점을 둔다.

소프트웨어를 개발할 때 이런 디자인 패턴 없이 개발한다면 뭐가 어디에 어떻게 개발되었는지도 헷갈리면서 개발할 수 밖에 없다. 이를 데이터 그 자체와 그걸 보여주는 부분으로 나눠서 개발한다면 편하지 않을까? 라는 측면에서 등장했다고 생각한다.
정의적으로는 크게 세 가지로 나뉜다.
코드를 보면 더 이해하기 쉽다. 게시판을 운영한다고 가정해보자.
게시판에는 제목과 내용이라는 데이터와, 이를 보여줄 수 있는 양식이 필요하다.
// MODEL
public class Question {
private String subject;
private String content;
public Question(String subject, String content) {
this.subject = subject;
this.content = content;
}
/* 멤버변수에 값을 저장하거나 불러오는 메소드들 */
public String getSubject() {
return subject;
}
}
모델은 위와 같이 DB 데이터를 매핑한 것이다.
// VIEW
public class QuestionView {
public void printQuestion(Question question) {
System.out.println(
"질문 제목은 " + question.getSubject() + "입니다");
}
}
뷰는 그 데이터를 보여줄 수 있는 것이다.
// CONTROLLER
public class QuestionController {
public String detail() {
Question question = new question("spring이 뭐에요?", "제곧내");
QuestionView.printQuestion(question);
}
}
컨트롤러는 모델에서 데이터를 가져와, 뷰와 연결시키는 역할을 한다.
간단해보이지만 중요한 것은 자기 할 것만 하는 식으로 분리시켜야 한다는 것이다. 예를 들어 모델이나 뷰에서 컨트롤러를 호출한다던가, 뷰 안에서 모델 객체를 생성한다던가 하는 구조는 가능하지만 MVC 패턴과 거리가 멀어진다고 할 수 있다. 그렇게 된다면 M-V-C가 서로 지나치게 의존하고 결합도가 강해질 것이다.
모든 서비스가 위처럼 간단하게 구현된다면 얼마나 좋을까? 하지만 현실은 더 복잡하다. 가령 게시판의 질문을 보여주고, 저장하고, 수정하는 컨트롤러를 만들었다고 하자. 근데 다른 게시판에서도 그 질문을 보여주려고 한다면, 해당 부분이 중복 코딩될 것이다.
이는 '역할을 분리'한다는 MVC의 키워드에 어긋난다. 따라서 멋쟁이들은 이미 MVC가 해야할 일을 더 분리시켜놨다. 거기서 등장한 것이 Repository와 Service라고 생각한다.

여기서 DAO(Data Access Object)란 데이터 접근 객체로서 말 그대로 DB에 접근해 데이터를 CRUD 할 수 있는 객체이다.
또한 DTO(Data Transfer Object)는 데이터 전달 객체로서 비즈니스 로직 없이 데이터만 가진 것이다.
예시 코드는 다음과 같다.
// REPOSITORY
public interface QuestionRepository extends JpaRepository<Question, Integer> {
// 특정 키워드가 포함된 질문을 찾는 DB에서 찾는 쿼리 함수
@Query("select "
+ "distinct q "
+ "from Question q "
+ "left outer join SiteUser u1 on q.author=u1 "
+ "left outer join Answer a on a.question=q "
+ "left outer join SiteUser u2 on a.author=u2 "
+ "where "
+ " q.subject like %:kw% "
+ " or q.content like %:kw% "
+ " or u1.username like %:kw% "
+ " or a.content like %:kw% "
+ " or u2.username like %:kw% ")
Page<Question> findAllByKeyword(@Param("kw") String kw, Pageable pageable);
}
리포지터리에서는 위와 같이 DB에 접근해서 데이터의 원형을 가져온다.
// SERVICE
public class QuestionService {
private final QuestionRepository questionRepository;
public Question getQuestion(Integer id) {
/* 질문을 가져오는 함수 */
/* 리포지터리에서 질문을 가져와 question 객체로 표현 */
}
public void create(String subject, String content, SiteUser siteUser) {
/* 질문을 만드는 함수 예시 */
Question q = new Question();
q.setContent(content);
q.setSubject(subject);
this.questionRepository.save(q);
}
public void delete(Question question) {
/* 질문을 삭제하는 함수 예시 */
this.questionRepository.delete(question);
}
}
서비스에서는 위와 같이 DTO를 받아서 리포지터리에 저장하도록 요청하거나, 리포지터리에서 받은 객체를 컨트롤러가 사용할 수 있도록 가공하고 처리한다. 이렇게 되면 컨트롤러에서는 원하는 서비스 객체들을 가져와서 마음대로 요리할 수 있다.
예를 들면 게시판에서는 질문 서비스와 댓글 서비스를 받아서, 특정 글에 달린 질문을 담는 html을 표현할 수 있을 것이다. 또한 질문 서비스의 create 함수를 사용하면 질문을 만들어달라는 요청을 서비스에 보내기만 하면 되므로, 문제가 생기면 어디서 잘못됐는지 알기도 쉽고 중복 코딩도 방지할 수 있다.
위에선 JPA에 대한 설명 없이도 충분한 설명이 될 것 같아 적지 않았지만, 어떤 언어든 ORM을 쓸 때 완전히 아는 상태가 아니면 위험하다고 생각한다.
가령 둘 이상의 Model에서 연관관계를 표현할 때 일대다(OneToMany), 다대일(ManyToOne) 같은 식으로 표현할 수 있다. 이때 어떤 부분이 외래키가 될지 모른다면, CRUD 기능 등을 추가했을 때 문제가 생길 수 밖에 없을 것이다.
따라서 다음 공부해야할 내용은 다음과 같다.
DI의 경우 객체지향과 추상화가 왜 필요한지 아직 잘 모르는 것 같아서 넣어보았다.
Spring framework에서는 위 내용을 반드시, 확실하게 알고, 어떤 상황에서도 적용할 수 있어야 한다고 느꼈다.
이해를 돕기 위해 참고한 유튜브 링크: [10분 테코톡] 🧀 제리의 MVC 패턴
이해를 돕기 위해 참고한 개념서 링크: MDN Web Docs 용어 사전: 웹 용어 정의 - MVC
이해를 돕기 위해 참고한 위키피디아: 모델-뷰-컨트롤러
이해를 돕기 위해 참고한 내 깃허브: github.com/byeonggwan/sbb