프로젝트/싹쓰리

[프로젝트] ServiceInterface와 ServiceImpl

라임온조 2023. 4. 23. 22:03

스프링에는 Service 계층이 있다. 이러한 Service 계층을 구현할 때는 그냥 기능별 Service 클래스를 만들어 구현을 하는 방법이 있고, 기능별로 ServiceInterface를 만들고 해당 Interface를 구현하는 ServiceImpl을 구현하는 방법이 있다. 이 두 방법에 대해 살펴보고 나의 프로젝트에 맞는 방법에 대해 고찰해보고자 한다.

 

1. ServiceImpl을 사용하는 방법

1) 개념

Service 별로 interface를 만들고, 해당 interface를 구현한 실제 serviceImpl를 만든다.

BoardServiceInterface를 만들어서 create라는 메서드를 명시하고 BoardServiceImpl을 만들어서 create 메서드를 실제로 구현하는 것.

 

2) 장점

  • 객체 지향 프로그래밍에서 인터페이스를 활용한 다형성을 구현할 수 있다
    • 하나의 interface에 명시해 놓은 메서드들을 다양한 구현체로 구현하면 하나의 인터페이스 아래에 다양한 구현체를 둘 수 있다. 예를 들어, AInterface에 create라는 메서드를 선언했고, AImpl에서 이 create라는 메서드를 실제로 구현했다. 하지만, 시간이 지난 후 create라는 메서드를 약간 다르게 수정하고 싶다. 하지만, 예전에 구현했던 create라는 메서드도 혹시 몰라서 남겨 놓으려고 한다. 이때, AInterface의 create라는 메서드를 실제로 구현하는 또 다른 AImpl2를 만들어서 새로운 create 메서드를 구현할 수 있다. 그러면 우리는 하나의 인터페이스에 선언되어 있는 create라는 메서드를 구현하는 2개의 구현체를 가지게 된 것이다.
  • 구현체 클래스를 변경하더라도 의존성 주입을 받은 클래스에서는 이를 신경쓰지 않아도 된다는 것을 의미하는 의존성 역전 원칙(Dependency Inversion Principle)에 따른 설계 방법을 적용할 수 있다.
    • 구현체 클래스를 변경하는 일이 발생해도 이 구현체 클래스를 호출하는 쪽에서는 이를 신경쓰지 않아도 된다. 예를 들어, Ainterface가 있고 이것의 구현체로 aImpl이 있다. 그리고 Acontroller는 Ainterface를 의존성 주입 받아서 사용하고 있다. 그런데, Ainterface의 구현체로 bImpl을 새로 만들었고 이를 이제부터 aImpl대신 bImpl을 사용하고자 한다. 하지만, 구현체를 새로 만들었다고 Acontroller에서 새롭게 의존성 주입을 받을 필요가 없다. Acontroller는 Ainterface를 이미 주입받았기 때문에 그것의 구현체가 어떻게 변경되었든지 상관없이 바로 bImpl을 사용할 수 있다. 물론, Ainterface에 있는 같은 메서드를 aImpl과 bImpl에서 오버라이딩 했다고 가정해야 한다. 그럼 구현체가 aImpl에서 bImpl로 바뀌어도 컨트롤러에서는 신경 쓸 필요가 없다.

 

+ 그럼 여기서 해당 의문이 들 수 있다.

그러면 BoardServiceInterface를 만들고 BoardServiceInterface를 구현하는 BoardServiceImpl1 에서 A라는 방법으로 create하는 메서드를 구현하고 BoardServiceInterface를 구현하는 BoardServiceImpl2를 만들어 B라는 방법으로 create하는 메서드를 구현하는 것과 - 1

BoardService를 만들고 A라는 방법으로 create하는 create1 메서드를 작성하고, B라는 방법으로 create하는 create2 메서드를 작성하는 것의 차이는 뭘까? - 2

 

구현 과정에서의 차이는 엄청나다고 할 수는 없지만 create메서드를 사용하는 측, 그러니까 BoardService를 의존성으로 주입받은 쪽은 영향을 받는다.

만약 1로 구현했다면 의존성 주입 받은 쪽은 딱히 할 일이 없다. 즉 의존성 역전 원칙에 맞는 개발이 가능해진다.

2로 구현했다면 의존성 주입 받은 쪽은 사용할 메서드에 따라 메서드명을 바꿔줘야 한다. 메서드 명이 create1과 create2로 달라지기 때문이다. 

 

2. ServiceImpl을 사용하지 않는 방법

1) 개념

interface를 만들지 않고 해당 기능에 맞는 service 코드를 바로 작성하는 것.

BoardService를 만들어서 여기에다가 create라는 메서드를 바로 구현하는 것.

 

2) 장점

  • 작성할 코드의 양이 줄어든다
    • interface 파일을 하나 안 만들어도 되니 작성해야 할 코드의 양이 줄어든다.

 

3. 적절한 상황

1) 특정 상황에서 다양한 구현체가 필요한 상황

예를 들어 파일 저장소를 다루는 서비스를 생각해보자. 파일을 저장하고, 읽고, 삭제하는 기능을 제공한다. 이때, 파일 저장소를 다루는 서비스 인터페이스를 만들고 이 인터페이스를 구현하는 다양한 구현체가 필요할 수 있다. 로컬 파일 시스템을 다루는 구현체, AWS S3를 다루는 구현체, 구글 드라이브를 다루는 구현체 등. 이렇게 만들고 난 이후 로컬 파일 시스템을 다루는 구현체를 사용한 컨트롤러, aws를 사용한 구현체를 다루는 컨트롤러를 만들어 기능을 제공할 수 있다.

물론 aws를 다루는 구현체에서 필요한 메서드와 구글 드라이브를 다루는 구현체에서 필요한 메서드가 동일할 경우에 이렇게 하면 좋다. File을 다루는 Interface가 있고 이걸 구현하는 다른 구현체들을 만들어 놓으면 의존성 역전 원칙에 따라 서비스 구현체를 가져다 쓰기가 편해지기 때문이다. 만약 aws 구현체에서 필요한 메서드와 구글 구현체에서 필요한 메서드가 다르다면 같은 인터페이스를 쓸 수 없어서 오히려 더 복잡해 질 수도 있다.

 

2) 확장성을 열어 두어야 하는 상황

예를 들어, 사용자의 요구사항이 바뀔 가능성이 커서 확장성을 열어 두어야 할 경우에는 interface를 두는 것이 좋다. 요구사항에 맞게 새로운 구현체를 만들어서 의존성 역전 원칙에 맞게 쉽게 유지보수를 할 수 있기 때문이다.

 

 

4. 나의 결정

내가 지금 하고 있는 프로젝트는 일단 확장성을 열어 두어야 하는 상황은 아니다. 졸업 프로젝트로써 기획자가 따로 있는 것이 아니기 때문에 기획자의 요청에 따라 무언가를 바꾸어야 하는 것이 아니다. 또한, 이 프로젝트로 스타트업을 차리거나 할 생각이 없어서 추후 확장이 될 가능성이 희박하다.

그래서 내가 처한 상황을 생각해봤을 때, 무조건 관습적으로 인터페이스를 생성하는 것은 비효율적이다. 따라서, 기본적으로는 인터페이스를 생성하지 않는 방식으로 할 생각이다.

하지만, 앞으로 개발해 나가다 보면 하나의 상황에서 다양한 구현체가 필요할 수도 있다. 이럴 경우가 생길 때만 인터페이스를 만들어서 적용해보고자 한다.