[Spring/TIL] Service, ServiceImpl 구조가 과연 필요할까

Marryakirise·2024년 1월 8일
0

Spring/TIL

목록 보기
2/5

🧐 개요

스프링 개발을 처음 시작할 때 오직 service만을 이용하여 개발을 시작했다. 그리고 service에 Interface를 쓰게된 이유는 의존성 역전 (DIP) 을 고려하여 사용하게 되었는데.. 근데 개발하면서 인터페이스도 작성하고, 인터페이스를 구현하는 클래스 코드도 따로 작성하게 되면, 수정하면 둘다 수정해야한다는 점에서 의문점을 가지게 되었다.


💡 왜 Service에 Interface를 사용할까?

OOP 관점

  • OOP : Object-Oriented Programming

- OCP

  • OCP : Open-Closed Principle : 개방-폐쇄 원칙

Interface 를 구체화하는 구현체 Service의 차이만 있을 뿐, 클라이언트 코드 controller 입장에서는 무엇이 변했는지 전혀 알 수 없다.

→ 변화에 닫혀있다

OCP 원칙에 따르면, Interface와 구현체가 1:N관계일 때, ServiceLayer의 구현체가 변경되더라도 클라이언트 코드는 전혀 수정할 필요가 없다.

→ 확장에 열려있다

- DIP

  • DIP : Dependency Inversion Principle : 의존 관계 역전의 원칙
  • 추상화에 의존해야하며 구체화에 의존하면 안된다.
  • 구체적 클래스가 아닌 인터페이스가 의존해야 하며 다형성을 이용해 유연한 코드를 만들어야 한다.

예시 1

하나는 todo 리스트를 메모리에서 가져오는 것이고, 하나는 DB와 같은 곳에서 가져온다고 생각해보자.

TodoService가 두 개의 구현체를 가지게 되고, 이때 유용하게 작용할 수 있다. 이렇게 하나의 서비스가 여러개의 구현체를 가지고 있다면, 다형성을 활용할 수 있다.

public interface TodoService {
    List<Todo> findAllTodos();
}

@Service
public class InMemoryTodoServiceImpl implements TodoService {
    public List<Todo> findAllTodos() {
        // TODO: Implement
        return new ArrayList<>();
    }
}

@Service
public class DatabaseTodoServiceImpl implements TodoService {
    public List<Todo> findAllTodos() {
        // TODO: Implement
        return new ArrayList<>();
    }
} 

예시 2

정률 할인 (10% 할인)에서 정액 할인 (1000원 할인)으로 변경할 때를 생각해보자.

만약 현재 정률 할인을 진행하더라도, 갑자기 정액할인으로 정책이 변경된다면, interface는 변경하지 않고 구현체만 간단하게 변경하면 된다.


💡 무조건적으로 Interface를 사용하는 게 좋을까?


내가 처음 고민했던 것처럼 구조가 복잡해져서 코드를 분석하는 과정에서도 인터페이스를 거쳐서 다시 그 인터페이스의 구현체들을 확인하는 단계가 추가되면서 오히려 더 불편해진다는 문제점이 있다.

또한, 실제로 대부분의 프로젝트나 예제 코드를 보면 Service 인터페이스와 구현체 클래스 사이의 관계가 1:1로 구성되어 있다.
그렇기 때문에 구현체가 2개 이상이 아니라면 인터페이스의 장점을 살릴 수 없기 때문에 인터페이스가 필요할 이유가 없지 않을까하는 의문점이 생긴다.


그러나, 위에서 말했듯이 인터페이스와 구현체를 분리해서 얻을 수 있는 장점은 서비스가 커지고 변화함에 따라서 얼마든지 구현체 클래스가 확장될 가능성을 가지고 있기 때문에 변화를 효과적으로 대처할 수 있다는 점이다.

예를 들면, 당근마켓 서비스 같은 경우, 일반 사용자도 존재하지만, 지역 게시판에 자신의 가게를 홍보할 수 있는 사장님의 권한을 가진 사용자도 존재한다.
이처럼, 서비스가 확장됨에 따라서 지금의 프로젝트가 1:1관계를 가지고 있다고 하지만, 얼마든지 1:N 관계를 가지는 서비스로 확장될 수 있다.


🪑 생각의 의자

Naming은 어떻게 할까?

여기서 우리는 그럼 MemberService, MemberServiceImpl1, MemberServiceImpl2 이렇게 네이밍을 지어야하나 고민할 수 있다.

MemberService : 회원 서비스 인터페이스
	- GeneralMemberService : 일반 회원 서비스 구현체 클래스 
	- CeoMemberService : 사장님 회원 서비스 구현체 클래스

다음과 같이 네이밍하는 것은 어떨지 추천해본다. 이런 구조가 정답은 아니지만, 기존 설계보다 충분히 이름만 가지고 동작과 역할이 유추가능한 클래스 이름을 가지고 있게 된다.

Impl라는 Package 만들고 그 안에 service 구현 클래스를 배치하는 이유는?

Service Interface와 Interface를 구현한 클래스를 별도의 패키지에 위치시키는 이유는 코드의 가독성을 높이고, 코드를 모듈화하고 분리하는데 도움이 되기 때문이다.


✔️ 결론

인터페이스와 구현체를 분리한 설계를 통한 이점들과 이유와 근거에 대해서 설계를 한 개발자 당사자는 명확히 이해를 해야한다는 것이 중요한 포인트이다.

참고 자료

https://see-one.tistory.com/1
https://junior-datalist.tistory.com/243
https://dev-coco.tistory.com/180
https://velog.io/@hsw0194/Spring-Boot%EC%97%90%EC%84%9C-interface%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C

profile
감자의 개발 일기

0개의 댓글

관련 채용 정보