Controller - Service - Repository 의 역할

Burpeeeee·2024년 8월 22일

스프링 프로젝트를 진행하려고 하면 우리는 기계적으로 Controller, Service, Repository 계층을 나누곤 한다. 이번 기회에 각 역할들은 어떤 역할을 하며 왜 나누어야 한는지 알아보도록 하겠다!

우선 MVC 먼저


간단히 말하자면 MVC는 디자인패턴 중 하나로 사용자가 controller를 조작하면 controller는 model을 통해서 데이터를 가져오고 그 정보를 바탕으로 시각적인 표현을 담당하는 View를 제어 사용자에게 전달하게 된다.

즉,

모델은 애플리케이션의 정보, 데이터를 나타내고
는 데이터 및 객체의 입력, 그리고 보여주는 출력을 담당한다. (데이타를 기반으로 사용자들이 볼 수 있는 화면을 말한다.)
컨트롤러는 데이터와 사용자인터페이스 요소들을 잇는 다리역할을 한다. 즉, 사용자가 데이터를 클릭하고, 수정하는 것에 대한 "이벤트"들을 처리하는 부분을 뜻한다.

MVC 패턴을 잘 이용하게 되면 사용자 인터페이스로부터 비즈니스 로직을 분리하여 애플리케이션의 시각적 요소나 그 이면에서 실행되는 비즈니스 로직을 서로 영향 없이 쉽게 고칠 수 있는 애플리케이션을 만들 수 있다.

🍀정리
모델: 데이터와 비지니스 로직 담당
뷰: 레이아웃과 화면 처리
컨트롤러(Controller): 모델과 뷰로 명령을 전달.
기능별로 코드를 분리하기 때문에 가독성,확장성,재사용성이 증가하고 유지보수도 용이하다.

갑자기 Service랑 Repository??

복잡한 대규모 프로그램의 경우 다수의 뷰와 모델이 컨트롤러를 통해 연결되기 때문에 컨트롤러가 불필요하게 커지는 현상이 나타난다. 그렇기에 실무(?)에서는 보통 Model-View-Contoller는 세부적으로 총 5단계를 거친다.

├── domain
│ └── Post.java
├── dto
│ ├── PostResponseDto.java
│ ├── PostListResponseDto.java
│ ├── PostSaveRequestDto.java
│ └── PostUpdateRequestDto.java
├── repository
│ └── PostRepository.java
├── service
│ └── PostService.java
├── controller
│ └── PostController.java

이 5단계를 3단계로 정리하자면
Model은 domain, dto, repository 가 이에 해당한다.
View는 HTML, CSS, JavaScript 등이 해당한다.
Controller: service, controller가 이에 해당한다.

그럼 Controller - Service - Repository 의 역할은?

여기서 역할은 간단히 정리 후 코드를 확인해 보자.

Domain 은 엔티티 선언을 통해 DB에 저장되는 객체들을 구현한다. 즉, 테이블의 각 Column들이 하나의 도메인이라 보면 된다.
Repository 는 데이터베이스에 직접적으로 접근해 도메인 객체를 DB에 저장하고 관리한다.
보통 Interface로 만들고 JpaRepository를 상속받아 사용한다.SQL문을 직접 입력할 수도 있다.
Controller는 웹 MVC의 컨트롤러 역할이다. Client가 요청을 하면 그 요청을 실질적으로 수행하는 서비스를 호출한다.
Service는 핵심 비즈니스 로직을 구현한다. 주로 리포지토리를 이용해 CRUD을 구현한다.

코드로 확인해보자

  1. Domain :Id와 이름에 대한 정보를 가지고 있는 멤버 엔티티이다.
package hello.hellospring.domain;

public class Member {

    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  1. Repository:
    다음은
// MemberRepository Interface
package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;

public interface MemberRepository {
    Member save(Member member);
    Optional<Member> findById(Long id);
    Optional<Member> findByName(String name);
    List<Member> findAll();
    
}
package hello.hellospring.repository;
        import hello.hellospring.domain.Member;
        import java.util.*;

public class MemoryMemberRepository implements MemberRepository {
    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;

    @Override
    public Member save(Member member) {
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }

    @Override
    public Optional<Member> findByName(String name) {
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();
    }

    public void clearStore() {
        store.clear();
    }
}

Service에서 DB에 있는 멤버에 관한 정보를 찾거나 수정, 삭제 하게 된다면 위 Repository를 이용한다.

JPA를 이용한다면 별도의 구현없이 아래처럼 적기만 하면 된다!

package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
        
public interface MemberRepository extends JpaRepository<Member, Long>{
    Member save(Member member);

    Optional<Member> findById(Long id);

    Optional<Member> findByName(String name);

    List<Member> findAll();
}
  1. Service: 회원 서비스 클래스이다. join은 회원가입, validateDuplucateMember는 회원 중복 확인 findMembers는 모든 멤버찾기, findOne은 해당하는 Id를 가지는 멤버 찾기의 기능을 한다. 코드를 보면 Repository를 이용해 DB에 접근하는 것을 볼 수 있다.
package hello.hellospring.service;
        import hello.hellospring.domain.Member;
        import hello.hellospring.repository.MemberRepository;
        import java.util.List;
        import java.util.Optional;

public class MemberService {
    private final MemberRepository memberRepository = new MemoryMemberRepository();


  // 회원가입
    public Long join(Member member) {
        validateDuplicateMember(member); //중복 회원 검증
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }

   // 전체 회원 조회
    public List<Member> findMembers() {
        return memberRepository.findAll();
    }

    public Optional<Member> findOne(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

  1. Controller:
package hello.hellospring.controller;
        import hello.hellospring.service.MemberService;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Controller;

@Controller
public class MemberController {
    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    @GetMapping(value = "/members/new")
    public String createForm() {
        return "members/createMemberForm";
    }

    @PostMapping(value = "/members/new")
    public String create(MemberForm form) {
        Member member = new Member();
        member.setName(form.getName());
        memberService.join(member);
        return "redirect:/";
    }

    @GetMapping(value = "/members")
    public String list(Model model) {
        List<Member> members = memberService.findMembers();
        model.addAttribute("members", members);
        return "members/memberList";
    }
}

@Autowired는 스프링이 연관된 객체를 스프링 컨테이너에서 알아서 찾아서 넣어준다. 나머지 @GetMapping 과 @PostMapping은 각 요청을 구분하는 것으로 GET 요청이 오는지 POST 요청이 오는지 구분한 후 뒤에 value에 들어가있는 주소로 해당하는 요청이 오면 해당 함수를 호출한다.

🍀정리
1. Cilent가 Request를 보낸다
2. Request URL에 알맞은 Controller가 수신받는다(@RestController,@Controller)
3. Controller는 넘어온 요청을 처리하기 위해 Service를 호출한다.
4. Service는 알맞은 정보를 가공하여 Controller에게 데이터를 넘긴다. (정보를 가공= 비지니스 로직 수행)
5. Controller는 service의 결과물을 Cilent에게 전달해준다.

  • 체크하기
    JPA를 사용하면 DAO는 직접 구현을 하지 않아도 되나...?

답:
JPA가 만들 수 있는 코드는 매우 가볍고 쉬운 쿼리를 대체하는 것이라서 복잡도가 높아지면 사용하기가 매우 어려움.JPA만으로만 수행한다면 잘못된 매핑이나 지연 로딩의 부적절한 사용 등으로 인해 성능 퍼포먼스가 발생할 수 있음.
-> JPA로 복잡도가 높은 쿼리를 짜거나 OR 복잡도가 높은 곳은 DAO로 같이 사용한다고 한다.

  • DAO란
    DB 서버에 접근하여 SQL 문을 실행할 수 있는 객체
    • 직접 DB에 접근 하여 CRUD 등을 조작
    • Service와 DB를 연결하는 역할
    • SQL을 사용하여 CRUD API를 동작

단순하게 페이지를 불러오고 DB정보를 한번에 불러오는 간단한 프로젝트의 경우 Service와 DAO는 차이가 거의 없을 수 있다.

[참고] https://filltheemptyspace.tistory.com/4
https://ksy0214k.tistory.com/156

profile
? 이 가득하지만 곧 !이 될

0개의 댓글