본문 바로가기

백엔드 잡학사전

[스프링 입문] 회원 관리 예제의 백엔드 개발 & 의존성 주입

Spring Boot에서의 핵심 폴더들 - controller, domain, repository, service

 

각각의 폴더를 만들고, 각각의 역할을 세분화함으로써 코드를 더 모듈화하고 유지보수하기 쉽게 만들 수 있음.

 

1 Controller

 

앱의 웹 계층을 담당하며, 사용자의 요청을 받아 처리하는 역할을 함. 주로 HTTP 요청 담당이라고 생각하면 됨.

@Controller 라는 어노테이션이 붙음. @GetMapping, @PostMapping 등도 쓰임.

 

 

2 Domain

 

도메인은 앱의 비즈니스 로직과 관련된 객체들을 정의하는 계층. 주로 Entity - 예를 들면 user나 member 등의 - 클래스들이 이 패키지에 위치함.

 

 

3 Repository

 

데이터 접근 계층을 담당하는데, DB와 상호작용하여 데이터를 CRUD하는 작업을 수행함. 주로 @Repository 어노테이션이 붙은 인터페이스들이 위치하며, DB 작업을 처리함.

 

 

4 Service

 

서비스 계층은 비즈니스 로직을 담당함. 컨트롤러에서 받은 요청을 처리하고, 필요한 데이터를 리포지토리에서 조회하거나 저장함. Repository 쪽에서 만든 메소드 등을 활용하여 메소드를 만들어주기 일쑤임.

 

 

 

▶ 비즈니스 요구사항 정리

 

매우 단순한 형태의 시스템을 가정한다.

- 데이터: 회원ID, 이름

- 기능: 회원 등록, 조회

- 아직 데이터 저장소가 선정되지 않았다는 가상의 시나리오

 

 

▶ 회원 도메인(객체), 리포지토리 만들기

 

hello_spring > domain > Member class

 

위에서 언급했던 "데이터" 부분을 만들고 각각에 대해서 getter와 setter를 만들어줬다.

그걸 도메인이라고 부르고 JAVA의 클래스로 표현을 한다. 회원 객체를 만든 것.

 

repository > MemberRepository interface

=> 이거는 그러니까, 우리가 만들게 될 수많은 멤버들 - 멤버 1, 멤버 2, ... 에 대한 작업을 할 곳이다.

 

원래는 이러고 끝나야 하는데, 우리는 DB를 아직 안 배웠기 때문에 가상으로 memory를 만들어준다, 아래처럼.

 

* 인터페이스와 일반적인 클래스는 다른 개념이다.

1 일반적인 클래스에는 메서드의 구현과 정의가 모두 들어가지만, 인터페이스에는 구현은 들어가지 않는다. 그러니까 인터페이스는 혼자서는 역할을 할 수 없다. 

2 어떤 클래스는 여러 개의 인터페이스를 상속받을 수 있다(다중상속). 다른 클래스를 어떤 클래스가 상속받는 것은 하나만 가능하다(단일상속).

3 그러니까 인터페이스는 한마디로, 클래스들에게 상속할 수 있도록 준비하는 도우미지, 플레이어 자체가 아니다. 즉 아무 클래스도 상속받지 않는다면 그 인터페이스는 무가치하다.

 

즉,  여러 클래스가 공통으로 가진 행동을 표준화하거나, 다형성을 지원하기 위해 만드는 게 인터페이스다.

 

 

hellospring > repository > MemoryMemberRepository

 

 

이렇게 관련된 구현이 다 끝났다. 이제 구현한 것들을 테스트로 확인해볼 시간!

 

 

▶ 회원 리포지토리 테스트 케이스 작성

 

보통 어떤 코드를 짜고 나면, 자바의 main 메서드 혹은 컨트롤러를 통해 실행하며 테스팅을 한다.

그러나 정식으로 여러 개를, 빠르게 테스트할 수 있는 게 자바에서는 JUnit이라는 테스트 프레임워크다.

 

hello_spring > MemoryMemberRepositoryTest

 

main과 test의 구조를 미러링한다고 생각하고, MemoryMemberRepositoryTest라는 클래스를 만들어준다. 다른 곳에서 쓸 일 없으니까 public 안해도 된다.

 

 

정말 간단하다. main에서 하는 것처럼 가상의 환경을 만들면 되고, 결과값을 Assertions.assertThat().isEqualTo();Assertions.assertEquals(); 등을 써서 확인해주면 그만이다.

 

주의할 점은, test를 여러개 만들고 돌려주면 순서에 상관없이 test가 진행되기 때문에 서로 독립적으로 되게끔, 공유하는 자원에 대해서 조심해줘야 한다. 위의 경우에도 repository를 공유하기 때문에 clear해주지 않으면 안 되는 것이다.

 

이걸 각각의 함수에 집어넣어줄 수 있겠지만 그럴 필요없이 @AfterEach 시전해주면 그만이다. 메소드는 store.clear().

 

 

▶ 회원 서비스 개발

 

서비스는 새로운 package를 만들어서 service 패키지를 프로젝트에 두면 된다. 지금껏 controller, domain, repository, service 이렇게 만들어준 바 있다.

 

회원가입의 join, validateDuplicateMember, findMembers, findOne 이렇게 4개의 메소드를 만들어 주었다.

 

 

▶ 회원 서비스 테스트

 

에러를 다루는 게 중요한데, 여기서는 아래와 같이 두개의 방법 중에 try-catch를 안 쓰려고 한다.

 

역시나 afterEach도 시전해줘야 하고.

 

그런데 우리가 MemberService에서 만든 녀석과 Testcase에서 만든 녀석이 서로 다른 인스턴스라는 크나큰 맹점이 있다. 이를 해결해주려면, MemberService를 memberRepository라는 객체를 주입받게끔 바꿔주면 된다. 이를 의존성 주입, 다른 말로 Dependency Injection이라고 부른다.

 

에서

 

 

의존성 주입 Dependency Injection

 

의존성 주입은

 

1 객체 간의 의존성이 있을 때 (서비스가 리포지토리를 사용하기 or 컨트롤러가 서비스를 사용하기 등)

2 객체의 결합도를 낮추고 싶을 때 - A가 B에 대해 자세히 몰라도, 결합도가 낮은 채로 의존성을 활용 가능함.

3 생명주기 관리를 프레임워크에 맡기고 싶을 때

4 테스트에 mock 객체를 활용하여 용이성을 높이고 싶을 때

 

쓴다.