controller-service-repository 디자인 패턴

디자인 패턴 중 controller-service-repository 패턴을 사용해서 클래스를 만들고, 저번에 연결했던 mysql에 API를 사용해서 데이터가 저장되도록 해 보았다. 

 

컨트롤러의 경우, @RestController 어노테이션(Annotation)을 사용해서 해당 클래스를 REST 컨트롤러로 등록해 준다. 

@RestController는 REST API 기능을 수행할 때 붙이고, @Controller는 REST API가 아닌, 일반 뷰를 렌더링하는 용도의 클래스에 등록한다. 

 

또한 디자인패턴 클래스들 중 컨트롤러가 가장 먼저 요청(request)을 받게 되는데, 해당 요청을 다음 클래스인 서비스(service)로 넘겨주기 위해서, private final 타입으로 service 멤버 변수를 정의한다. 

 

private final로 선언하는 이유는 한번 서비스 클래스와 컨트롤러 클래스가 맵핑된 뒤 그 맵핑이 바뀌지 않게 하기 위해서이다.

 

또한 @Autowired를 생성자에 붙여서 컨트롤러가 생성될 때 매개변수로 서비스를 받도록 한다. 서비스와 컨트롤러가 같이 생성되도록 하기 위해서이다. 

 

그런데 스프링에서는 객체의 생성을 스프링 컨테이너에서 관리하므로, 서비스와 컨트롤러를 할당할 때 new 생성자를 쓰면 안 된다. 그냥 매개변수로 받은 서비스를 멤버 변수에 연결하면 된다. 

 

컨트롤러 클래스 내부에는 각 메소드를 정의할 수 있는데, 메소드 하나당 하나의 API가 된다. 

각 메소드에는 @GetMapping, @PostMapping 등의 어노테이션을 붙여서 해당 API가 어떤 HTTP 방식으로 작동할지를 명시한다. 또한 @GetMapping의 경우 @RequestParam 어노테이션을 붙여서 쿼리 변수를 받을 수도 있다. 

 

MemberController.java

@RestController
public class MemberController {

    private final MemberService memberService;

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

    @GetMapping("/member")
    public Member member(@RequestParam(value = "name", defaultValue = "user") String name){
		// API logic
    }
}

 

서비스 클래스도 마찬가지이다. 컨트롤러 클래스가 로직 가장 위단에서 요청을 처리하는 반면(통신 관련 로직 등), 서비스 클래스에는 소위 말하는 비즈니스 로직이 포함된다. 

 

@Service 어노테이션으로 해당 클래스를 서비스 클래스로 등록한다. 마찬가지로 private final 멤버 변수로 리포지토리(repository)를 등록한 뒤, 생성자에 @Autowired를 붙여서 서비스 클래스가 생성될 때 리포지토리 클래스가 같이 생성 및 매칭되도록 한다. 

 

MemberService.java

@Service
public class MemberService {

    private final MemberRepository memberRepository;

    @Autowired
    public MemberService(MemberRepository memberRepository){
        this.memberRepository = memberRepository;
    }

    public Member join(Member member){
        // business logic
    }
}

 

리포지토리 클래스의 경우 로직이 상대적으로 간단하다. 정확하게는 리포지토리 클래스가 아니라 리포지토리 인터페이스가 된다. 

 

리포지토리 인터페이스를 만들고, 해당 인터페이스가 <도메인 클래스 타입, 도메인 클래스 id 타입>JpaRepository를 상속받도록 한다. 

 

해당 JpaRepository 안에는 DB에 접근할 때 사람들이 많이 사용하는 메소드들(findById 등)이 들어 있기 때문에, 이 인터페이스를 상속하면 해당 메소드를 별도로 정의하지 않고 사용할 수 있다. 

 

다만 JpaRepository에서는 ID같은 PK 필드에 대해서만 findById 메소드를 제공하므로, 다른 컬럼을 기준으로 찾고 싶다면 해당 인터페이스에 추가로 메소드를 정의해주면 된다. 

 

MemberRepository.java

@Repository
public interface MemberRepository extends JpaRepository<Member, Integer> {
    Optional<Member> findByName(String name);

}

 

마지막으로 데이터베이스에 저장할 객체가 될 도메인(domain) 클래스를 정의한다. 

 

PK인 필드에는 @Id를 붙이고, DB의 AUTO_INCREMENT 옵션을 적용하고 싶다면 @GeneratedValue(strategy=GenerationType.AUTO)를 적용한다. 

 

Member.java

@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String name;

}

 

이후 실행하면 다음과 같이 생성된 객체에 대한 json이 잘 나오는 것을 볼 수 있다. 

 

또한 mysql에도 데이터가 잘 저장된다. 

 

참고한 포스트

https://spring.io/guides/gs/rest-service/

https://velog.io/@leesomyoung/SpringBoot-SQLException-No-database-selected

 

+ Recent posts