비즈니스 요구사항 정리 - 가상의 시나리오
- 데이터: 회원ID, 이름
- 기능: 회원 등록, 조회
- 아직 데이터 저장소가 선정되지 않음
일반적인 웹 애플리케이션 계층 구조
- 컨트롤러: 웹 MVC의 컨트롤러 역할
- 서비스: 비즈니스 도메인 객체를 활용한 핵심 비즈니스 로직 구현
- ex) 회원은 중복가입이 되지 않는다
- 리포지토리: 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
- 도메인: 비즈니스 도메인 객체
- ex) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨
클래스 의존관계
- 아직 데이터 저장소가 선정되지 않아서, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계
- 데이터 저장소는 RDB, NoSQL 등등 다양한 저장소를 고민중인 상황으로 가정
- 개발을 진행하기 위해서 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용
- JDBC, MyBatis 등을 바꿔줄 수 있다는 가정 하에 설계
회원 도메인과 리포지토리 만들기
package hello.hello_spring.domain;
public class Member {
private Long id;
private String name;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
}
앞에서 도메인에 회원ID, 이름이 필요하다고 정의했기 때문에
- Member 도메인에 id와 name을 선언해주고
- getter setter까지 선언해줬다.
package hello.hello_spring.repository;
import hello.hello_spring.domain.Member;
import java.util.List;
import java.util.Optional;
public interface MemberRepository {
Member save(Member member);
Optional<Member> findMyId(Long id);
Optional<Member> findMyName(String name);
List<Member> findAll();
}
package hello.hello_spring.repository;
import hello.hello_spring.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> findMyId(Long id) {
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findMyName(String name) {
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
}
sequence로 키 값을 생성해주는 건 실무에서는 보통 저런구조로 활용하진 않지만,
간단한 예제를 만들어보는 것이기 때문에 키값을 member를 생성할 때마다 id = ++sequence로 사용해줬다.
@Override
public Optional<Member> findMyId(Long id) {
return Optional.ofNullable(store.get(id));
}
Optional로 묶는 이유는, 데이터가 없을 경우 에러가 나는 것을 방지하기 위해서 사용한다.
여기서는 Optional.ofNullable()을 사용해서 store.get(id)값이 없을 때 null이 가능하게 만들어줬다.
@Override
public Optional<Member> findMyName(String name) {
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
}
람다식을 사용해서 member 객체가 저장되어있는 store를 돌면서, name과 같은 회원이 존재하는지 확인해준다. (for문과 같음)
만약, 이름이 name인 회원이 존재하지 않을 경우 null을 반환한다.
람다식을 잘 쓰는 편은 아닌데, 따로 공부해봐야겠다는 생각이 들었다 .. 그나마 sort할때정도 람다식 쓰는 🥺
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
HashMap에 있는 value들만 ArrayList로 넣기 위해 ArrayList<>(store.values())를 사용했다.
보통 Map에 있는 value들을 꺼내보고 싶을 땐 ArrayList를 많이 사용한다.
그리고 보통 스프링으로 서비스를 구현할 때 인터페이스를 구현하는 구조로 개발을 진행한다.
public class MemoryMemberRepository implements MemberRepository
이렇게 인터페이스를 implement(상속)한 구현체에 기능들을 정의하곤 한다.
- 인터페이스: MemberRepository는 서비스가 제공해야 할 기능(메서드)을 정의
- 구현체: MemoryMemberRepository은 그 기능을 실제로 구현하는 클래스
강의를 들으면서 코드를 구현하면서 문득, 이런 의문이 들었다.
Q) 왜 Service를 Interface로 생성하고, 인터페이스를 상속받은 구현체에 기능을 정의하는걸까?
1. 유연한 설계 및 느슨한 결합
Interface는 객체의 사용 방법/제공해야할 기능(메서드)을 정의한 타입이다. 인터페이스는 "무엇을 할 수 있는지"만 약속하고, "어떻게 동작하는지"는 구현체에게 맡긴다. 이 덕분에 사용자는 구현에 의존하지 않고 기능을 사용할 수 있다.
즉, 개발 코드를 수정하지 않고 사용하는 객체를 변경할 수 있도록 해준다.
ex) PaymentService라는 인터페이스를 만들고, KakaoPayService, TossPayService 등을 구현하면, 실제 사용하는 쪽에서는 어떤 결제 수단을 쓰든 상관없이 PaymentService만 의존하면 된다.
2. 유지보수와 확장성
서비스 로직이 커지거나 새로운 방식이 추가돼도 인터페이스는 그대로 두고 구현체만 수정하거나 추가하면 된다.
ex) 기존 EmailNotiService 외에 SmsNotiService를 추가해도 NotiService 인터페이스는 그대로 사용 가능.
문득 궁금해져서 검색을 해보니 Springboot 2.X에서는 spring AOP 사용시 반드시 인터페이스가 존재해야 프록시 객체를 정상적으로 만들어 낼 수 있었다고 한다. 아마 그래서 인터페이스를 구현한 서비스 형태가 관행적으로 내려온게 아닐까 추측하는 사람들이 많았다. 또, 그렇기 때문에 꼭 interface 구조로 개발을 할 필요는 없는 것 같다고 하는 사람도 있었다.
어찌됐든 결론적으로 서비스를 인터페이스로 분리하면 유연한 설계, 테스트 용이성, 유지보수성을 모두 갖춘 확장 가능한 구조를 만들 수 있다는 것!
'Backend > Spring 입문' 카테고리의 다른 글
[Spring] 5. 회원관리 예제(웹 MVC 개발) (3) | 2025.04.29 |
---|---|
[Spring] 4. 스프링 빈과 의존관계 (1) | 2025.04.29 |
[Spring] 3. 회원 관리 예제 - 회원 레포지토리 테스트 케이스 작성 / 서비스 개발 / 서비스 테스트 (0) | 2025.04.25 |
[Spring] 2. 스프링 웹 개발 기초 (0) | 2025.04.19 |
[Spring] 2. 프로젝트 환경설정 (0) | 2025.04.18 |