Когда мы хотим знать, в каком состоянии находятся системы, с которыми наше приложение взаимодействует, мы используем механизм метрик. Самым распространенным механизмом работы с метриками в приложениях на Spring Boot является micrometer.

Для интеграций по HTTP с использованием REST очень удобно использовать spring-boot-starter-actuator. Актуатор уже из коробки предоставляет набор из http_client_requests и http_server_requests метрик с разбивкой по uri, method и результату.

Но остались еще проекты, которые взаимодействуют по протоколу SOAP. Хотя экосистема Spring (как и ванильная Java) предоставляют возможность собирать веб-сервисы и клиенты для протокола SOAP, но коробочного решения для снятия метрик с таких клиентов не существует.

Что такое SOAP

SOAP - это довольно старый и редко используемый протокол для обмена произвольными сообщениями в формате XML. Надеюсь вам повезет и вы никогда с ним не столкнетесь)

Представим, что появилось требование - реализовать механизм снятия метрик с клиентов, которые взаимодействуют по SOAP. Считать будем количество отправленных запросов и результат выполнения запроса с разбивкой по uri.

Для проектов на Java и Spring есть два способа построить веб-клиента для протокола SOAP - Spring WebService и JAX-WS. Рассмотрим решения под оба этих варианта, поскольку единый механизм создать не получится.

Снятие метрик для клиента с использованием Spring WebService

В модуле Spring WebService для создания клиента применяется класс WebServiceTemplate, который может быть расширен с помощью набора различных ClientInterceptor. Интерфейс ClientInterceptor предоставляет 4 метода.

Нам интересен

void afterCompletion(MessageContext messageContext, Exception ex),

поскольку он вызывается во всех случаях - отправки запроса, получения ответа (в том числе с Fault), выброса какого-либо исключения в процессе обмена.

Реализуем свою имплементацию интерфейса. В ней переопределяем только метод afterCompletion, и реагируем только на ответ (или исключение). Факт отправки запроса в метриках нам не интересен.

public class SpringWsMetricsInterceptor implements ClientInterceptor {
    private static final Pattern pattern = Pattern.compile("^[^#]*?://.*?(?<pathGroup>/.*)$");

    private final MeterRegistry meterRegistry;

    public SpringWsMetricsInterceptor(MeterRegistry meterRegistry) {
      this.meterRegistry = meterRegistry;
    }

    @Override
    public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
        return true;
    }

    @Override
    public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
        return true;
    }

    @Override
    public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
        return true;
    }

    @Override
    public void afterCompletion(MessageContext messageContext, Exception e) throws WebServiceClientException {
        if (messageContext.hasResponse() || e != null) {
            //в метрики пишем либо успешный ответ, либо ошибку
            TransportContext context = TransportContextHolder.getTransportContext();
            String url = context.getConnection().getUri().getPath();
            String outcome = e != null ? "SERVER_ERROR" : "SUCCESS";
            String status = e != null ? e.getMessage() : "200";

            Matcher matcher = pattern.matcher(url);
            String uri = matcher.find() ? matcher.group("pathGroup") : url;
            Counter.builder("soap.ws.client.requests")
                    .tag("outcome", outcome)
                    .tag("status", status)
                    .tag("uri", uri)
                    .register(meterRegistry)
                    .increment();
        }
    }
} 

В результате получаем набор метрик вида

soap_ws_client_requests_total{outcome="SUCCESS",status="200",uri="/path/to/service",}

Снятие метрик для клиента с использованием JAX-WS

Для клиентов, реализованных с использованием JAX-WS, к сожалению, нет возможности построить красивый и укладывающийся в парадигму Spring вариант снятия метрик.

Поскольку механизм JAX-WS предлагает только вызов метода интерфейса, сгенерированного плагином, и явно не имплементированного в коде, то остается только использовать аннотацию @Timed. Необходимо в клиенте реализовать набор методов, повторяющих методы JAX-WS интерфейса и проаннотировать их.

Допустим, есть wsdl-схема, из который будет сгенерирован интерфейс:

@WebService(name = "MyJaxWsInterface", targetNamespace = "urn:myJaxWs:interface")
public interface MyJaxWsInterface {
    @WebMethod(operationName = "Create")
    @WebResult(name = "createResponse", targetNamespace = "urn:myJaxWs:types", partName = "result")
    public CreateResponse create(
        @WebParam(name = "createRequest", targetNamespace = "urn:myJaxWs:types", partName = "request")
        CreateRequest request)
        throws IllegalUsageException_Exception, InternalSystemErrorException_Exception
    ;
}

Нужно реализовать враппер, расширяющий SOAP интерфейс, методы которого проаннотированы @Timed.

public class JaxWsClientWrapper implements MyJaxWsInterface {
    private MyJaxWsInterface myJaxWsInterface;
    
    public JaxWsClientWrapper(MyJaxWsInterface myJaxWsInterface) {
        this.myJaxWsInterface = myJaxWsInterface;
    }

    @Override
    @Timed
    public CreateResponse create(CreateRequest request){
        return myJaxWsInterface.createRequest(request);
    }
}

В результате получаем набор стандартных метрик, где будет указано имя класса, имя вызываемого метода и наличие или отсутствие exception при вызове. К сожалению, при таком подходе нет возможности получить url и status ответов в метриках.

Комментарии (0)