[Spring] MVC 패턴

Byeonggwan Kang·2023년 7월 16일

MVC 패턴에 대해 정리한다.

백엔드 개발을 공부하다보니 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가 서로 지나치게 의존하고 결합도가 강해질 것이다.


Repository, Service

모든 서비스가 위처럼 간단하게 구현된다면 얼마나 좋을까? 하지만 현실은 더 복잡하다. 가령 게시판의 질문을 보여주고, 저장하고, 수정하는 컨트롤러를 만들었다고 하자. 근데 다른 게시판에서도 그 질문을 보여주려고 한다면, 해당 부분이 중복 코딩될 것이다.

이는 '역할을 분리'한다는 MVC의 키워드에 어긋난다. 따라서 멋쟁이들은 이미 MVC가 해야할 일을 더 분리시켜놨다. 거기서 등장한 것이 Repository와 Service라고 생각한다.

  • 리포지터리는 모델로 생성된 DB 테이블에 접근한다.
  • 서비스는 DAO와 컨트롤러 사이에서 데이터를 가공하고 전달한다.

여기서 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 기능 등을 추가했을 때 문제가 생길 수 밖에 없을 것이다.

따라서 다음 공부해야할 내용은 다음과 같다.

  • DB, ORM(Object Relational Mapping) - JPA, , SQL Mapper - MyBatis
  • DI(Dependency Injection), IoC(Inversion of Control)

DI의 경우 객체지향과 추상화가 왜 필요한지 아직 잘 모르는 것 같아서 넣어보았다.

Spring framework에서는 위 내용을 반드시, 확실하게 알고, 어떤 상황에서도 적용할 수 있어야 한다고 느꼈다.


출처

이해를 돕기 위해 참고한 유튜브 링크: [10분 테코톡] 🧀 제리의 MVC 패턴

이해를 돕기 위해 참고한 개념서 링크: MDN Web Docs 용어 사전: 웹 용어 정의 - MVC

이해를 돕기 위해 참고한 위키피디아: 모델-뷰-컨트롤러

이해를 돕기 위해 참고한 내 깃허브: github.com/byeonggwan/sbb

0개의 댓글