SOAP-to-REST를 사용하여 마이그레이션 및 통합 단순화하기

Jinhua Luo

March 31, 2023

Technology

1. 웹 서비스란 무엇인가?

웹 서비스는 World Wide Web Consortium(W3C)에 의해 네트워크를 통해 상호 운용 가능한 기계 간 상호 작용을 지원하도록 설계된 소프트웨어 시스템으로 정의됩니다.

웹 서비스는 특정 작업 또는 일련의 작업을 수행하며, Web Services Description Language (WSDL)라는 표준 XML 표현으로 서비스를 설명합니다. 서비스 설명은 메시지 형식(작업을 상세히 설명하는 데 사용됨), 전송 프로토콜 및 위치를 포함하여 서비스와 상호 작용하는 데 필요한 모든 세부 정보를 제공합니다.

다른 시스템은 일반적으로 HTTP와 XML 직렬화 및 기타 웹 관련 표준을 함께 사용하여 SOAP 메시지를 통해 웹 서비스와 상호 작용합니다.

웹 서비스의 아키텍처 다이어그램은 다음과 같습니다(서비스 브로커는 선택 사항임):

웹 서비스 아키텍처

*이미지 출처 (CC 3.0 BY-SA 라이선스): https://en.wikipedia.org/wiki/Web_service

WSDL 인터페이스는 서비스가 어떻게 구현되었는지에 대한 상세 정보를 숨기므로, 서비스의 사용은 서비스를 구현하는 하드웨어 또는 소프트웨어 플랫폼 및 서비스를 작성하는 데 사용된 프로그래밍 언어와 독립적입니다.

웹 서비스 기반 애플리케이션은 느슨하게 결합되고, 컴포넌트 지향적이며, 기술 간 구현입니다. 웹 서비스는 단독으로 사용되거나 다른 웹 서비스와 함께 사용되어 복잡한 집계 또는 비즈니스 트랜잭션을 수행할 수 있습니다.

웹 서비스는 Service-oriented architecture (SOA)의 구현 단위로, 모놀리식 시스템을 대체하기 위해 사용되는 설계 방법입니다. 대규모 시스템은 여러 웹 서비스로 분해될 수 있으며, 이는 외부에 비즈니스 로직을 제공하는 큰 블랙박스로 결합될 수 있습니다. 인기 있는 컨테이너 기반 마이크로서비스는 웹 서비스의 최신 대체품입니다. 그러나 많은 레거시 시스템이 이미 웹 서비스를 기반으로 구현 및 운영되고 있으므로, 기술의 급속한 발전에도 불구하고 이러한 시스템과의 호환성은 여전히 과제로 남아 있습니다.

WSDL (Web Services Description Language)

WSDL은 웹 서비스를 설명하는 데 사용되는 XML 표기법입니다. WSDL 정의는 클라이언트에게 웹 서비스 요청을 작성하는 방법을 지시하고 웹 서비스 제공자가 제공하는 인터페이스를 정의합니다.

WSDL 정의는 여러 부분으로 나뉘며, 웹 서비스의 논리적 인터페이스와 물리적 세부 사항을 지정합니다. 물리적 세부 사항에는 HTTP 포트 번호와 같은 엔드포인트 정보 및 SOAP 메시지 내용을 표현하는 방법과 사용할 전송 방법을 지정하는 바인딩 정보가 포함됩니다.

WSDL 1.1 및 WSDL 2.0 문서에 의해 정의된 개념의 표현

이미지 출처 (CC 3.0 BY-SA 라이선스): https://en.wikipedia.org/wiki/Web_Services_Description_Language

  • WSDL 파일은 여러 서비스를 포함할 수 있습니다.
  • 서비스는 여러 포트를 포함할 수 있습니다.
  • 포트는 URL 주소를 정의하며(각 포트마다 다를 수 있음), 여러 작업을 포함할 수 있습니다.
  • 각 작업에는 입력 유형과 출력 유형이 포함됩니다.
  • 유형은 메시지 구조를 정의하며, 메시지를 구성하는 필드, 각 필드의 데이터 유형(중첩될 수 있음) 및 허용되는 필드 수를 포함합니다.

1.1 SOAP란 무엇인가

SOAP는 웹 서비스 상호 작용에서 사용되는 XML 메시지 형식입니다. SOAP 메시지는 일반적으로 HTTP 또는 JMS를 통해 전송되지만, 다른 전송 프로토콜도 사용할 수 있습니다. WSDL 정의는 특정 웹 서비스에서 SOAP 사용을 설명합니다.

SOAP의 두 가지 일반적으로 사용되는 버전은 SOAP 1.1과 SOAP 1.2입니다.

SOAP 구조

이미지 출처 (CC 3.0 BY-SA 라이선스): https://en.wikipedia.org/wiki/SOAP

SOAP 메시지는 다음 부분을 포함합니다:

  • 헤더 메타데이터, 일반적으로 비어 있음
  • 본문
    • WSDL에 정의된 메시지 유형
    • 성공적인 응답 외에도 응답 유형에 대한 구조화된 오류 메시지가 있음

예시:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header></SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <ns2:getCountryResponse xmlns:ns2="http://spring.io/guides/gs-producing-web-service">
      <ns2:country>
        <ns2:name>Spain</ns2:name>
        <ns2:population>46704314</ns2:population>
        <ns2:capital>Madrid</ns2:capital>
        <ns2:currency>EUR</ns2:currency>
      </ns2:country>
    </ns2:getCountryResponse>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

1.2 REST란 무엇인가

웹 서비스는 추상적인 개념으로, 어떤 방식으로든 구현될 수 있습니다. 예를 들어, REST는 인기 있는 구현 방법 중 하나입니다.

REST는 Representational State Transfer의 약자로, 문자 그대로 표현 계층의 상태 전송을 의미합니다. Roy Thomas Fielding은 2000년 그의 박사 논문에서 REST라는 용어를 제안했습니다. 당시 인터넷이 급성장하면서 소프트웨어 개발 및 네트워크와의 상호 작용에 대한 실용적인 정의가 필요했습니다.

오랜 시간 동안 소프트웨어 연구는 주로 소프트웨어 설계의 분류와 설계 방법의 진화에 초점을 맞추었습니다. 다른 설계 선택이 시스템 행동에 미치는 영향을 객관적으로 평가하는 경우는 드물었습니다. 반면, 네트워크 연구는 주로 시스템 간 통신 행동의 세부 사항과 특정 통신 메커니즘의 성능을 개선하는 데 초점을 맞추었으며, 종종 상호 작용 프로토콜을 변경하는 것보다 애플리케이션의 상호 작용 스타일을 변경하는 것이 전체 성능에 더 큰 영향을 미친다는 사실을 간과했습니다. 내 글은 기능적이고 성능이 좋으며 통신에 적합한 네트워크 기반 애플리케이션 소프트웨어의 아키텍처 설계를 이해하고 평가하는 것을 목표로 합니다.

웹사이트에 접근하는 것은 클라이언트와 서버 간의 상호 작용 과정을 나타냅니다. 이 과정에서 데이터와 상태의 변화가 반드시 발생합니다. HTTP 프로토콜은 상태를 유지하지 않으므로 모든 상태는 서버 측에 저장됩니다. 따라서 클라이언트가 서버를 조작하려면 서버 측에서 "상태 전송"을 일으키는 어떤 수단을 사용해야 합니다. 이 전송은 표현 계층에 구축되므로 "표현 계층 상태 전송"이라고 합니다.

REST의 네 가지 기본 원칙은 다음과 같습니다:

  1. HTTP 동사 사용: GET, POST, PUT, DELETE;
  2. 상태 비저장 연결, 서버는 너무 많은 컨텍스트 상태를 저장하지 않아야 함; 즉, 각 요청은 독립적임;
  3. 각 리소스에 URI 설정;
  4. x-www-form-urlencoded 또는 JSON을 데이터 형식으로 사용;

SOAP를 REST로 변환하면 사용자가 전통적인 웹 서비스에 RESTful 방식으로 접근할 수 있게 되어 SOAP 클라이언트의 개발 비용을 줄일 수 있습니다. 더 나아가, 동적으로 모든 웹 서비스에 적응할 수 있는 제로 코드 개발이 가능하다면 더욱 완벽할 것입니다.

REST의 가장 큰 장점은 스키마가 없어 개발이 편리하며, JSON이 가독성이 높고 중복이 적다는 것입니다.

2. SOAP-to-REST 프록시의 전통적인 구현 방법

2.1 수동 템플릿 변환

이 방법은 웹 서비스의 각 작업에 대해 요청 및 응답 변환 템플릿을 제공해야 하며, 많은 다른 게이트웨이 제품에서도 사용됩니다.

APISIX body transformer 플러그인을 사용하여 간단한 SOAP-to-REST 프록시를 생성하고 이 방법을 시도할 수 있습니다.

예를 들어, WSDL 파일의 CountriesPortServicegetCountry 작업에 대해 XML 형식의 요청 템플릿을 구성합니다.

여기서 JSON의 name 필드를 getCountryRequest의 name 필드에 채웁니다.

req_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1' | awk '{$1=$1};1' | tr -d '\r\n'
<?xml version="1.0"?>
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
 <soap-env:Body>
  <ns0:getCountryRequest xmlns:ns0="http://spring.io/guides/gs-producing-web-service">
   <ns0:name>{{_escape_xml(name)}}</ns0:name>
  </ns0:getCountryRequest>
 </soap-env:Body>
</soap-env:Envelope>
EOF
)

응답에 대해 XML-to-JSON 템플릿을 제공해야 하며, 이는 조금 더 복잡할 수 있습니다(특히 SOAP 버전 차이를 고려해야 하는 경우). 이는 응답이 성공적인지 여부를 결정해야 하기 때문입니다:

  • 성공적인 응답의 경우, 필드를 JSON에 직접 매핑할 수 있습니다.
  • 실패한 응답(즉, fault)의 경우, 별도의 JSON 구조가 필요하며, 특정 선택적 필드가 있는지 여부를 결정해야 합니다.
rsp_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1' | awk '{$1=$1};1' | tr -d '\r\n'
{% if Envelope.Body.Fault == nil then %}
{
   "currency":"{{Envelope.Body.getCountryResponse.country.currency}}",
   "population":{{Envelope.Body.getCountryResponse.country.population}},
   "capital":"{{Envelope.Body.getCountryResponse.country.capital}}",
   "name":"{{Envelope.Body.getCountryResponse.country.name}}"
}
{% else %}
{
   "message":{*_escape_json(Envelope.Body.Fault.faultstring[1])*},
   "code":"{{Envelope.Body.Fault.faultcode}}"
   {% if Envelope.Body.Fault.faultactor ~= nil then %}
   , "actor":"{{Envelope.Body.Fault.faultactor}}"
   {% end %}
}
{% end %}
EOF
)

APISIX 라우팅을 구성하고 테스트를 수행합니다:

curl http://127.0.0.1:9180/apisix/admin/routes/1 \
    -H 'X-API-KEY: xxx' -X PUT -d '
{
    "methods": ["POST"],
    "uri": "/ws/getCountry",
    "plugins": {
        "body-transformer": {
            "request": {
                "template": "'"$req_template"'"
            },
            "response": {
                "template": "'"$rsp_template"'"
            }
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "localhost:8080": 1
        }
    }
}'
curl -s http://127.0.0.1:9080/ws/getCountry \
    -H 'content-type: application/json' \
    -X POST -d '{"name": "Spain"}' | jq
{
  "currency": "EUR",
  "population": 46704314,
  "capital": "Madrid",
  "name": "Spain"
}
# Fault response
{
  "message": "Your name is required.",
  "code": "SOAP-ENV:Server"
}

이 방법은 WSDL 파일의 각 작업 정의와 각 작업에 해당하는 웹 서비스 주소를 수동으로 이해해야 합니다. WSDL 파일이 크고 많은 작업을 포함하며 복잡한 중첩 유형 정의가 있는 경우, 이 방법은 매우 번거롭고 디버깅이 어려우며 오류가 발생하기 쉽습니다.

2.2 Apache Camel

https://camel.apache.org/

Camel은 잘 알려진 Java 통합 프레임워크로, 다양한 프로토콜과 비즈니스 로직을 변환하는 라우팅 파이프라인을 구현하는 데 사용되며, SOAP-to-REST는 그 사용 사례 중 하나입니다.

Camel을 사용하려면 WSDL 파일을 다운로드하고 가져와 SOAP 클라이언트에 대한 스텁 코드를 생성하고 Java 코드를 작성해야 합니다:

  • REST 엔드포인트 정의
  • 프로토콜 변환 경로 정의, 예를 들어 JSON 필드가 SOAP 필드에 어떻게 매핑되는지

예를 들어, 온도 단위 변환을 위한 웹 서비스를 고려해 보겠습니다:

https://apps.learnwebservices.com/services/tempconverter?wsdl

  1. Maven을 사용하여 WSDL 파일을 기반으로 SOAP 클라이언트 코드를 생성합니다.

cxf-codegen-plugin은 웹 서비스에 접근하기 위한 SOAP 클라이언트 엔드포인트를 생성합니다.

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.cxf</groupId>
      <artifactId>cxf-codegen-plugin</artifactId>
      <executions>
        <execution>
          <id>generate-sources</id>
          <phase>generate-sources</phase>
          <configuration>
            <wsdlOptions>
              <wsdlOption>
                <wsdl>src/main/resources/TempConverter.wsdl</wsdl>
              </wsdlOption>
            </wsdlOptions>
          </configuration>
          <goals>
            <goal>wsdl2java</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
  1. SOAP 클라이언트 빈 작성.

나중에 Camel 경로를 정의할 때 사용할 빈 이름을 cxfConvertTemp로 기억해 두세요.

import com.learnwebservices.services.tempconverter.TempConverterEndpoint;
@Configuration
public class CxfBeans {
    @Value("${endpoint.wsdl}")
    private String SOAP_URL;
    @Bean(name = "cxfConvertTemp")
    public CxfEndpoint buildCxfEndpoint() {
        CxfEndpoint cxf = new CxfEndpoint();
        cxf.setAddress(SOAP_URL);
        cxf.setServiceClass(TempConverterEndpoint.class);
        return cxf;
    }
}
  1. 먼저 다운스트림 REST 경로를 작성합니다.

이 경로에서 RESTFul 스타일의 URL과 해당 매개변수 정의를 볼 수 있으며, 각 URL에 대한 다음 홉 경로를 정의합니다. 예를 들어, /convert/celsius/to/fahrenheit/{num}은 URL의 마지막 부분을 매개변수(double 유형)로 취하고 이를 다음 홉 경로 direct:celsius-to-fahrenheit에 제공합니다.

rest("/convert")
    .get("/celsius/to/fahrenheit/{num}")
    .consumes("text/plain").produces("text/plain")
    .description("Convert a temperature in Celsius to Fahrenheit")
    .param().name("num").type(RestParamType.path).description("Temperature in Celsius").dataType("int").endParam()
    .to("direct:celsius-to-fahrenheit");
  1. 마지막으로 업스트림 SOAP 경로와 업스트림과 다운스트림 간의 변환을 작성합니다.
from("direct:celsius-to-fahrenheit")
    .removeHeaders("CamelHttp*")
    .process(new Processor() {
        @Override
        public void process(Exchange exchange) throws Exception {
            // SOAP 요청 초기화
            // 다운스트림 매개변수 num을 본문에 채웁니다. 이는 간단한 double 유형입니다.
            CelsiusToFahrenheitRequest c = new CelsiusToFahrenheitRequest();
            c.setTemperatureInCelsius(Double.valueOf(exchange.getIn().getHeader("num").toString()));
            exchange.getIn().setBody(c);
        }
    })
    // SOAP 작업 및 네임스페이스 지정
    // application.properties 파일에 정의됨
    .setHeader(CxfConstants.OPERATION_NAME, constant("{{endpoint.operation.celsius.to.fahrenheit}}"))
    .setHeader(CxfConstants.OPERATION_NAMESPACE, constant("{{endpoint.namespace}}"))
    // WSDL에 의해 생성된 SOAP 클라이언트 빈을 사용하여 패키지를 전송합니다.
    .to("cxf:bean:cxfConvertTemp")
    .process(new Processor() {
        @Override
        public void process(Exchange exchange) throws Exception {
            // SOAP 응답 처리
            // 본문에 double 유형 값을 채우고 문자열로 반환합니다.
            // 문자열을 다운스트림에 반환합니다.
            MessageContentsList response = (MessageContentsList) exchange.getIn().getBody();
            CelsiusToFahrenheitResponse r = (CelsiusToFahrenheitResponse) response.get(0);
            exchange.getIn().setBody("Temp in Farenheit: " + r.getTemperatureInFahrenheit());
        }
    })
    .to("mock:output");
  1. 테스트
curl localhost:9090/convert/celsius/to/fahrenheit/50
Temp in Farenheit: 122.0

Camel을 사용하여 SOAP-to-REST를 수행하려면 모든 작업에 대한 경로와 변환 로직을 Java 코드로 정의해야 하며, 이는 개발 비용이 발생합니다.

마찬가지로, WSDL에 많은 서비스와 작업이 포함된 경우, Camel을 사용하여 프록시를 수행하는 것도 번거로울 수 있습니다.

2.3 결론

전통적인 방법의 단점을 요약해 보겠습니다.

모듈Camel
WSDL수동 파싱Maven을 통한 코드 생성
업스트림수동 파싱자동 변환
본문 정의판단 및 변환을 위한 템플릿 제공변환 코드 작성
매개변수 가져오기Nginx 변수코드에서 정의하거나 SOAP 클라이언트 인터페이스를 호출하여 가져오기

이 두 가지 접근 방식 모두 개발 비용이 발생하며, 새로운 웹 서비스마다 이 개발 비용이 반복됩니다.

개발 비용은 웹 서비스의 복잡성에 비례합니다.

3. APISIX의 SOAP-to-REST 프록시

전통적인 프록시 접근 방식은 변환 템플릿을 제공하거나 변환 코드를 작성해야 하며, 이는 사용자가 WSDL 파일을 깊이 분석해야 하고 상당한 개발 비용이 발생합니다.

APISIX는 WSDL 파일을 자동으로 분석하고 각 작업에 대한 변환 로직을 제공하는 자동화된 접근 방식을 제공하여 사용자의 개발 비용을 없앱니다.

이것은 엔터프라이즈 플러그인입니다. 자세한 정보는 문의하세요.

APISIX SOAP-to-REST 프록시

3.1 코드 없는 자동 변환

APISIX SOAP 프록시 사용:

  • WSDL 파일을 수동으로 파싱하거나 가져올 필요가 없음
  • 변환 템플릿을 정의할 필요가 없음
  • 변환 또는 결합 코드를 작성할 필요가 없음.

사용자는 WSDL의 URL만 구성하면 되며, APISIX가 자동으로 변환을 수행합니다. 모든 웹 서비스에 적합하며, 특정 요구 사항에 대한 2차 개발이 필요하지 않은 일반적인 프로그램입니다.

3.2 동적 구성

  • WSDL URL은 모든 경로에 바인딩될 수 있으며, 다른 APISIX 리소스 객체와 마찬가지로 런타임에 업데이트될 수 있습니다. 구성 변경은 APISIX를 재시작하지 않고도 동적으로 적용됩니다.
  • WSDL 파일에 포함된 서비스 URL(여러 URL이 있을 수 있음), 즉 업스트림 주소는 자동으로 인식되고 SOAP 업스트림으로 사용되며, 사용자가 파싱하고 구성할 필요가 없습니다.

3.3 구현 메커니즘

  • WSDL URL에서 WSDL 파일 내용을 가져와 분석 후 프록시 객체를 자동으로 생성합니다.
  • 프록시 객체는 프로토콜 변환을 담당합니다:
    • JSON 입력을 기반으로 준수하는 SOAP XML 요청을 생성합니다.
    • SOAP XML 응답을 JSON 응답으로 변환합니다.
    • 웹 서비스에 접근하고 SOAP 프로토콜 세부 사항(예: fault 유형 응답)을 자동으로 처리합니다.
    • SOAP1.1 및 SOAP1.2와 몇 가지 확장 기능(예: WS-Addressing)을 지원합니다.

3.4 예제 구성

SOAP 플러그인의 구성 매개변수 설명:

매개변수필수?설명
wsdl_urlWSDL URL, 예: https://apps.learnwebservices.com/services/tempconverter?wsdl

테스트:

# SOAP 플러그인을 사용하여 APISIX 라우팅 구성
# 단일 경로가 모든 작업을 실행할 수 있으며, 작업 이름은 URL 매개변수를 사용하여 지정됩니다.
# 이는 동적 프록시의 이점을 반영하며, WSDL 파일의 각 작업을 수동으로 분석할 필요가 없습니다.
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
    -H 'X-API-KEY: xxx' -X PUT -d '
{
    "methods": ["POST"],
    "uri": "/getCountry",
    "plugins": {
        "soap": {
            "wsdl_url": "http://localhost:8080/ws/countries.wsdl"
        }
    }
}'
curl 'http://127.0.0.1:9080/getCountry' \
    -X POST -d '{"name": "Spain"}'
# 호출 성공
HTTP/1.1 200 OK
Date: Tue, 06 Dec 2022 08:07:48 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/2.99.0
{"currency":"EUR","population":46704314,"capital":"Madrid","name":"Spain"}
# 호출 실패
HTTP/1.1 502 Bad Gateway
Date: Tue, 03 Jan 2023 13:43:33 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/2.99.0
{"message":"Your name is required.","actor":null,"code":"SOAP-ENV:Server","subcodes":null,"detail":null}

4. 결론

오늘날 웹 서비스는 많은 기업 사용자가 전통적인 SOAP 기반 웹 서비스를 사용하여 서비스를 제공하는 수준까지 발전했습니다. 역사적인 이유와 비용 고려로 인해 이러한 서비스는 항상 RESTful 서비스로 완전히 리팩토링하기에 적합하지 않습니다. 결과적으로 많은 기업 사용자들 사이에서 SOAP-to-REST에 대한 수요가 큽니다.

APISIX가 제공하는 SOAP-to-REST 플러그인은 제로 코드 프록시 기능을 달성할 수 있으며, 동적으로 구성할 수 있고 2차 개발이 필요하지 않아 기업 사용자의 제로 비즈니스 마이그레이션 및 통합에 유리합니다.

Tags: