본문 바로가기

JAVA

[JAVA] 자바 인터페이스(Interface)는 어떤 경우에 사용하나?(실전 프로젝트 경험담)

여러분은 자바로 프로젝트할 때  인터페이스(Interface) 많이 사용하시나요? 

전 공공기관 SI 프로젝트 위주로 스프링 프레임워크를 이용해서 개발을 진행하다 보니 Serviec Interface를 제외하고는 거의 사용할 일이 없었습니다. 

 

공공기관 SI 프로젝트는 대부분 데이터 위주이다 보니 대부분의 코드는 아래처럼 흘러갑니다. 

  1. 사용자 요청(HTTP request)
  2. Controller 가 받아서
  3. Service로 넘기고
  4. Service는 DAO를 호출하고
  5. DAO는 DB에서 데이터를 조회해서 다시 Service로 리턴하고
  6. Service는 데이터를 이용해서 로직을 수행하고
  7. 결과를 다시 Controller로 넘기고
  8. Controller는 다시 사용자에게 응답하게 되죠.

대부분 이런 로직이다 보니 Service 의 Interface를 제외하곤 자바 인터페이스를 사용할 일이 없습니다. 

 

그러던 중 개발 7년만에 처음으로 Java Interface를 제대로 사용할만한 요구사항을 만났습니다. 

오늘은 그 당시 실제 프로젝트에서 개발했던 사례를 통해 Java Interface에 대해서 알아보도록 하겠습니다. 

 

 

어떤 경우에 사용했나? 

웹 서비스되고 있는  API가 10개가 있고, 계속 API는 추가되는 상황이었습니다. API별로 각각 서버가 다른 경우도 있고 호출하는 URL구조(도메인)도 다른 경우였죠. 

 

각 사용자가 각각의 API를 별도로 호출하는 구조

 

그런데 사용자들이 혼란스러워하고 동일한 기능(API 인증키 체크 및 로그 저장 등)이 각 API마다 중복되거나 조금씩 서로 다르기 때문에 통합하는 게 요구사항이었습니다. 

 

중간에 API 통합 Gate를 거쳐서 각각의 API를 호출하는 구조

 

 

인터페이스를 사용하지 않은 경우 코드

Spring Controller 클래스

@RequestMapping("/api/{apiKey}")
@ResponseBody
public String api(HttpServletRequest request, HttpServletResponse response, 
	@PathVariable String apiKey){
	....
    return getApiServiceResult(apiKey); //api Servie 호출
    
}

 

Controller는 실제 호출할 API를 구분할 수 있는 apiKey를 파라미터로 받은 후에 Service에 넘깁니다. 

 

만약 인터페이스를 사용하지 않는다면 아래와 같이 로직을 작성해야 합니다.

왜냐하면 각 API마다 서로 다른 URL을 가지고 있고, 리턴값 구조(XML, JSON, 성공 시, 실패 시)도 다르기 때문이죠.

게다가 각 API마다 동일한 행위를 하는 메서드인데 메서드명까지 다르면 코드가 더 난잡해지게 되겠죠.

 

인터페이스를 적용하지 않은 경우 Spring Service 클래스

String apiUrl;
String result;
boolean isError;

if(apiKey =="A"){

  ApiA api = new ApiA();

  apiUrl = api.getUrl(); //실제 호출한 API URL을 가져온다.
  result = Util.getHttpUrlResult(apiUrl); //http url 통신을 통해 API 호출 후 결과값 리턴
  isError = api.isError(result);//결과값에 에러메세지가 있는지 확인
  if(isError){
      String errorMsg = api.getErrorMsg(result); //에러메세지만 추출
      dao.insertErrorMsg(errorMsg);//에러 메세지 DB 저장(나중에 왜 에러가 났는지 관리자가 쉽게 확인하기 위함)
  }
  
}else if(apiKey =="B"){

  ApiB api = new ApiB();

  apiUrl = api.getApiUrl(); //실제 호출한 API URL을 가져온다.
  result = Util.getHttpUrlResult(apiUrl); //http url 통신을 통해 API 호출 후 결과값 리턴
  isError = api.isErrorCheck(result);//결과값에 에러메세지가 있는지 확인
  if(isError){
      String errorMsg = api.getErrorMsg(result); //에러메세지만 추출
      dao.insertErrorMsg(errorMsg);//에러 메세지 DB 저장(나중에 왜 에러가 났는지 관리자가 쉽게 확인하기 위함)
  }
  
}else if(apiKey =="C"){

  ApiC api = new ApiC();

  apiUrl = api.getHttpUrl(); //실제 호출한 API URL을 가져온다.
  result = Util.getHttpUrlResult(apiUrl); //http url 통신을 통해 API 호출 후 결과값 리턴
  isError = api.checkError(result);//결과값에 에러메세지가 있는지 확인
  if(isError){
      String errorMsg = api.getError(result); //에러메세지만 추출
      dao.insertErrorMsg(errorMsg);//에러 메세지 DB 저장(나중에 왜 에러가 났는지 관리자가 쉽게 확인하기 위함)
  }
}
.....

 

인터페이스를 이용해서 코드 개선

먼저 자바 인터페이스에 대한 개념을 다시 생각해보겠습니다.

어떤 객체가 있고 그 객체가 특정한 인터페이스를 사용한다면 그 객체는 반드시 인터페이스의  메소드들을 구현해야 한다. 즉, 구현하는 모든 클래스에 대해 특정한 메서드가 반드시 존재하도록 강제한다.
쉽게 말해 설계도라고 생각하면 된다.

흔히 사용하는 Map의 구조를 다시 생각해보면 어떻게 설계해야 할지 감이 올 겁니다. 

Map map = new HashMap();

Map map = new TreeMap();

 

 

먼저 각각의 API 클래스가 공통적으로 가지고 있는 정보와 해야할 일을 정의합니다. (설계)

Interface 정의

public interface ApiI {
	String apiUrl(); //실제 호출할 API URL    
	boolean isError(); //에러 응답인지 여부
	String getErrorMsg(); //에러 메세지
}

 

그리고 각 클래스가 interface를 implements 하도록 합니다. 

//API A 
public Class ApiA implements ApiI{

  private static final String URL = "http://www.aservice.com/api/a.do";
  @Override
  public String getApiUrl(){
    return URL;
  }
  
  @Override
  public boolean isErrorCheck(String resultMsg) {
    //API 결과값이 XML 형태
    Document jdomdoc = new SAXBuilder().build(new StringReader(resultMsg));
    Element root= jdomdoc.getRootElement();

    if("error".equals(root.getName())) { //에러에 대한 XML 구조는 API마다 전부 다르다
      return true;
    }else{
      return false;
    }
  }
  
  @Override
  public String getErrorMsg(String resultMsg) {
    ...XML에서 에러 메세지만 추출해서 리턴한다.
  }
}


//API B
public Class ApiB implements ApiI{

  private static final String URL = "http://www.btets.com/api/b/btest.do";
  @Override
  public String getApiUrl(){
    return URL;
  }
  
  @Override
  public boolean isErrorCheck(String resultMsg) {
    //API 결과값이 JSON 형태
    JSON jsonObj2 = JSONObject.fromObject( resultMsg );
    JSONObject json = (JSONObject) JSONSerializer.toJSON( jsonObj2);
    String reCode = json.getString("error");

    if(!"00".equals(reCode)) {
      return true;
    }else{
      return false;
    }      
  }
  
  @Override
  public String getErrorMsg(String resultMsg) {
    ...JSON 에서 에러 메세지만 추출해서 리턴한다.
  }
}

 

실제 API를 호출하는 로직이 어떻게 바뀌는지 보겠습니다. 

인터페이스를 적용 한 Spring Service 클래스

String apiUrl;
String result;
boolean isError;
ApiI api = null;

if(apiKey =="A"){
  api = new ApiA();
  
}else if(apiKey =="B"){
  api = new ApiB();
  
}else if(apiKey =="C"){
  api = new ApiC();
  
}
.....

apiUrl = api.getUrl(); //실제 호출한 API URL을 가져온다.
result = Util.getHttpUrlResult(apiUrl); //http url 통신을 통해 API 호출 후 결과값 리턴
isError = api.isError(result);//결과값에 에러메세지가 있는지 확인
if(isError){
  String errorMsg = api.getErrorMsg(result); //에러메세지만 추출
  dao.insertErrorMsg(errorMsg);//에러 메세지 DB 저장(나중에 왜 에러가 났는지 관리자가 쉽게 확인하기 위함)
}
  

 

소스코드가 좀 더 간결하고 깔끔하게 변경되었네요. 

api를 생성하는 if .. 구문은 switch 나 enum 등을 이용해서 좀 더 깔끔하게 수정할 수 있겠죠. 

앞으로 API 클래스를 추가하더라도 인터페이스를 통해 구현할 메서드를 강제하기 때문에 API 클래스 이외에 다른 부분을 수정하지 않아도 됩니다.

 

다음엔 java enum을 이용해서 좀 더 깔끔하게 소스코드를 개선하는 방법을 알아보겠습니다. 

 

2019/05/15 - [JAVA] - [JAVA] 자바 enum 을 활용해서 코드 심플하게 개선하기(이런 것까지 가능하다)