Cómo Crear un Custom CSM Reachability Service

Esta guía proporcionará un ejemplo de cómo utilizar CSM Reachability funcionalidades para crear un custom implementación.

Paso 1: Genere la API Punto final

Si un nuevo API El endpoint es requerido, generelo junto con todos los esquemas y clases de modelo necesarios. Siga el CSM Reachability estructure creando una interfaz que servirá como el punto de entrada (puerto) al nuevo servicio.

Usaremos un ficticio iban-reachability punto final como ejemplo, planeando exponer esta lógica a través de un HTTP API:

openapi: 3.0.1
info:
  title: Custom CSM Reachability Service API V1
  version: ${project.version}
  description: APIs for IBAN Reachability
servers:
  - url: http://localhost:8080
    description: Local server URL
paths:
  /iban-reachability:
    get:
      tags:
        - iban-reachability
      description: Using creditor iban to discover which CSMs is creditor reachable by
      operationId: get-csm-reachability
      parameters:
        - name: processingEntity
          in: query
          description: >-
            Exact match on the processing entity on behalf of which the source
            payment instruction was being processed
          required: true
          schema:
            type: string
          example: '001'
        - name: creditorIban
          in: query
          description: IBAN for which to check which CSMs are reachable
          required: true
          schema:
            type: string
        - name: transferCurrency
          in: query
          description: Filter results by currency
          schema:
            type: string
# omitted for brevity

Paso 2: Separe los Módulos de Modelo y Puerto

Es una buena práctica separar el modelo generado y el módulo de puerto, permitiendo que se utilicen de manera independiente si es necesario.

Utilizamos la API especificación para generar las clases del modelo:

<? xml version="1. 0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4. 0. 0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4. 0. 0 http://maven.apache.org/xsd/maven-4. 0. 0.xsd">
    <modelVersion>4. 0. 0</modelVersion>
    <parent>
        <groupId>com.iconsolutions.ipf.payments.csm.reachability</groupId>
        <artifactId>iban-reachability-parent</artifactId>
        <version>0. 0. 1-SNAPSHOT</version>
    </parent>

    <artifactId>modelo-de-alcance-iban</artifactId>

    <dependencies>
        <dependency>
            <groupId>io.swagger.core.v3</groupId>
            <artifactId>swagger-annotations</artifactId>
        </dependency>
        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
        </dependency>
        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <!-- https://openapi-generator.tech/ -->
                <groupId>org.openapitools</groupId>
                <artifactId>openapi-generator-maven-plugin</artifactId>
                <dependencies>
                    <!-- Dependiendo del módulo de la API, vamos a generar interfaces a partir de su especificación -->
                    <dependency>
                        <groupId>com.iconsolutions.ipf.payments.csm.reachability</groupId>
                        <artifactId>iban-reachability-service-api</artifactId>
                        <version>${project.version}</version>
                    </dependency>
                </dependencies>
                <ejecuciones>
                    <!-- Generar clases DTO -->
                    <ejecución>
                        <id>generar-modelo-openapi</id>
                        <phase>generar-fuentes</phase>
                        <goals>
                            <goal>generar</goal>
                        </goals>
                        <configuration>
                            <inputSpec>static/iban-reachability.yaml</inputSpec>
                            <skipIfSpecIsUnchanged>true</skipIfSpecIsUnchanged>

                            <!-- Consulte https://openapi-generator.tech/docs/generators/spring/ para opciones -->
                            <generatorName>primavera</generatorName>
                            <configOptions>
                                <dateLibrary>java8</dateLibrary>
                                <generateBuilders>true</generateBuilders>
                                <openApiNullable>false</openApiNullable>
                                <useSwaggerAnnotations>false</useSwaggerAnnotations>
                                <booleanGetterPrefix>es</booleanGetterPrefix>
                                <!-- Mantenga la compatibilidad hacia atrás con el código existente utilizando constructores de Lombok -->
                                <!-- Para ser estrictamente compatible con el esquema existente, no incluya propiedades nulas en los JSONs -->
                                <additionalModelTypeAnnotations>
                                    @lombok. NoArgsConstructor
                                    @lombok. Builder(toBuilder = true)
                                    @lombok. AllArgsConstructor(access = lombok. AccessLevel. PRIVATE)
                                    @com.fasterxml.jackson.annotation. JsonInclude(com.fasterxml.jackson.annotation. JsonInclude. Include. NON_NULL)
                                </additionalModelTypeAnnotations>
                                <useSpringBoot3>true</useSpringBoot3>
                            </configOptions>

                            <!-- Solo genere modelos en este módulo -->
                            <generateModels>true</generateModels>
                            <modelPackage>com.iconsolutions.ipf.csmreachability.dto.v1</modelPackage>

                            <!-- Las interfaces/APIs se generarán más adelante en un módulo separado -->
                            <generateApis>false</generateApis>
                            <generateApiTests>false</generateApiTests>
                            <generateApiDocumentation>false</generateApiDocumentation>
                            <generateModelTests>false</generateModelTests>
                            <generateModelDocumentation>false</generateModelDocumentation>

                            <!-- Skip all the extra stuff that gets generated -->
                            <generateSupportingFiles>false</generateSupportingFiles>
                        </configuration>
                    </execution>
                    <ejecución>
                        <id>generar-iban-alcance</id>
                        <goals>
                            <goal>generar</goal>
                        </goals>
                        <configuration>
                            <inputSpec>static/iban-reachability.yaml</inputSpec>
                            <skipIfSpecIsUnchanged>true</skipIfSpecIsUnchanged>
                            <generatorName>openapi</generatorName>
                            <configOptions>
                                <outputFileName>iban-reachability-service.json</outputFileName>
                            </configOptions>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Luego, añadimos la dependencia del modelo al módulo de puerto para utilizar las clases generadas.

<? xml version="1. 0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4. 0. 0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4. 0. 0 http://maven.apache.org/xsd/maven-4. 0. 0.xsd">
    <modelVersion>4. 0. 0</modelVersion>
    <parent>
        <groupId>com.iconsolutions.ipf.payments.csm.reachability</groupId>
        <artifactId>iban-reachability-parent</artifactId>
        <version>0. 0. 1-SNAPSHOT</version>
    </parent>

    <artifactId>iban-reachability-service-api-port</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.iconsolutions.ipf.payments.csm.reachability</groupId>
            <artifactId>modelo-de-alcance-iban</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>
</project>

Y finalmente, definimos un puerto de servicio:

/**
 * <p>
 * Interface for providing list of reachable csm's
 * based on input provided (iban, sort code, account number)
 * </p>
 */
public interface IbanReachabilityService {
    Mono<ReachabilityDtoWrapper> getReachableCsm(IbanReachabilityCriteria ibanReachabilityCriteria);
}

Paso 3: Implemente el REST Controlador

Implemente el REST controlador del generado API:

@RestController
@RequiredArgsConstructor
public class IbanReachabilityController implements IbanReachabilityApi {

    private final IbanReachabilityService ibanReachabilityService;

    @Override
    public Mono<ResponseEntity<ReachabilityDtoWrapper>> getCsmReachability(String processingEntity,
                                                                           String creditorIban,
                                                                           String transferCurrency,
                                                                           ServerWebExchange exchange) {
        var criteria = IbanReachabilityCriteria.builder()
                .processingEntity(processingEntity)
                .creditorIban(creditorIban)
                .transferCurrency(transferCurrency)
                .build();

        return ibanReachabilityService.getReachableCsm(criteria)
                .map(ResponseEntity::ok)
                .switchIfEmpty(Mono.just(notFound().build()));
    }
}

Paso 4: Utilice el Manejo de Errores de Alcance

Implemente un controlador de errores específico y los atributos de error necesarios. Aquí tiene algunos ejemplos:

public class ErrorHandler extends AbstractErrorWebExceptionHandler {

    private static final RequestPredicate CSM_REACHABILITY_PATHS = Stream.of(
                    "/iban-reachability")
            .map(RequestPredicates::path)
            .reduce(RequestPredicate::or)
            .orElseThrow();

    public ErrorHandler(ErrorAttributes errorAttributes,
                        ApplicationContext applicationContext,
                        ServerCodecConfigurer serverCodecConfigurer) {
        super(errorAttributes, new WebProperties.Resources(), applicationContext);
        super.setMessageReaders(serverCodecConfigurer.getReaders());
        super.setMessageWriters(serverCodecConfigurer.getWriters());
    }

    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(org.springframework.boot.web.reactive.error.ErrorAttributes errorAttributes) {
        return RouterFunctions.route(CSM_REACHABILITY_PATHS, this::renderErrorResponse);
    }

    private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
        var errorPropertiesMap = getErrorAttributes(request, ErrorAttributeOptions.defaults());
        return ServerResponse.status(ErrorAttributes.httpStatusFor(getError(request)))
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(errorPropertiesMap));
    }
}
public class ErrorAttributes extends DefaultErrorAttributes {

    public static HttpStatus httpStatusFor(Throwable ex) {
        return ex instanceof ServerWebInputException
                || ex instanceof InvalidRequestException
                ? HttpStatus.BAD_REQUEST
                : HttpStatus.INTERNAL_SERVER_ERROR;
    }

    @Override
    public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        var error = getError(request);
        var httpStatus = httpStatusFor(error);
        var map = super.getErrorAttributes(request, options);
        map.put("status", httpStatus);
        map.put("messages", getErrorMessage(error));
        map.put("error", httpStatus.getReasonPhrase());
        return map;
    }

    private List<String> getErrorMessage(Throwable error) {
        return error.getMessage() == null ? Collections.emptyList() : List.of(error.getMessage());
    }
}

Paso 5: Implemente la Capa de Servicio

Primero, agregue la dependencia para el CSM Reachability service:

<dependency>
    <groupId>com.iconsolutions.ipf.payments.csm.reachability</groupId>
    <artifactId>csm-reachability-service</artifactId>
    <version>{csm-reachability.version}</version>
</dependency>

Además, añada la dependencia para el módulo de puerto construido anteriormente para implementar el servicio.

Paso 6: Defina las Propiedades de Configuración

Para usar DPS Implementación directa añada las siguientes dependencias:

<dependency>
    <groupId>com.iconsolutions.ipf.payments.csm.reachability</groupId>
    <artifactId>csm-reachability-api-direct</artifactId>
    <version>{csm-reachability.version}</version>
</dependency>
<dependency>
    <groupId>com.iconsolutions.ipf.core.dynamicsettings.v2</groupId>
    <artifactId>dynamic-processing-settings-service-adapter</artifactId>
    <version>{dynamic-processing-settings.version}</version>
</dependency>

Defina las siguientes propiedades en la configuración:

actor-system-name = csm-reachability-service

ipf.csm-reachability.settings-api.connection = direct

ipf.dps-api.client-type = "direct"

Para utilizar el DPS implementación de conectores, establezca la segunda propiedad en http y refiérase a Conectores DPS.

Paso 7: Construir Comprobaciones de Validación

Con el CSM Reachability lógica disponible como dependencias, construya una verificación de validación que utilice AgentClearingSettings data:

@RequiredArgsConstructor
public class AgentClearingSettingsCheck implements ValidationCheck<IbanReachabilityByCurrenciesValidationData> {

    // Inject an interface provided by `csm-reachability-api` and implemented in
    // `csm-reachability-api-direct` and `csm-reachability-api-connector`
    private final AgentClearingSettingsQuery agentClearingSettingsQuery;

    @Override
    public Mono<IbanReachabilityByCurrenciesValidationData> checkAndEnrich(IbanReachabilityByCurrenciesValidationData validationData) {
        var criteria = Map.<String, Object>of("processingEntity", validationData.getProcessingEntity());
        // use the query interface to build validation validation logic
        return Mono.fromCompletionStage(agentClearingSettingsQuery.getAgentClearingSettings(criteria))
                .map(Response::getValue)
                .filter(SettingsDTO::hasSettings)
                .flatMapIterable(SettingsDTO::getSettings)
                .filter(SettingsValidationUtil::isNotInactiveApprovalPendingSetting)
                .map(SettingDTO::getPayload)
                .collectList()
                .map(itemsList -> itemsList.stream().collect(toMap(AgentClearingSettings::getAgentUniqueId, Function.identity())))
                .map(agentClearingSettingsCache -> enrichValidationData(validationData, agentClearingSettingsCache));
    }

Conecte todas las verificaciones de validación relacionadas en un servicio:

@RequiredArgsConstructor
public class IbanReachabilityByCurrencyServiceImpl {

    // validation checks built using existing `csm-reachability` logic
    private final AgentSettlementSettingsCheck agentSettlementSettingsCheck;
    private final ValidateCsmReachabilityCheck validateCsmReachabilityCheck;
    private final AgentClearingSettingsCheck agentClearingSettingsCheck;

Paso 8: Construya el Custom Servicio

Finalmente, construya su propio custom servicio:

@RequiredArgsConstructor
public class IbanReachabilityServiceImpl implements IbanReachabilityService {

    private final Validator validator;
    private final IbanReachabilityByOnUsServiceImpl ibanReachabilityByOnUsService;
    private final IbanReachabilityByCurrencyServiceImpl ibanReachabilityByCurrencyService;

    @Override
    public Mono<ReachabilityDtoWrapper> getReachableCsm(IbanReachabilityCriteria criteria) {
        return validate(criteria)
                .flatMap(validatedCriteria -> {
                    CounterPartyIdentifier cpi = toCounterPartyIdentifier(validatedCriteria);
                    return ibanReachabilityByOnUsService.getReachableCsm(cpi, validatedCriteria.getProcessingEntity(), validatedCriteria.getTransferCurrency())
                            .zipWith(ibanReachabilityByCurrencyService.getReachableCsms(cpi, validatedCriteria.getProcessingEntity(), validatedCriteria.getTransferCurrency()));
                })
                .map(IbanReachabilityServiceImpl::buildResult);
    }

    private Mono<IbanReachabilityCriteria> validate(IbanReachabilityCriteria criteria) {
        var violations = validator.validate(criteria);
        if (!violations.isEmpty()) {
            return Mono.error(new ConstraintViolationException(violations));
        }
        return Mono.just(criteria);
    }

    private static CounterPartyIdentifier toCounterPartyIdentifier(IbanReachabilityCriteria criteria) {
        return CounterPartyIdentifier.builder()
                .identifier(criteria.getCreditorIban())
                .identifierType(IDENTIFIER_TYPE_IBAN)
                .build();
    }

    private static ReachabilityDtoWrapper buildResult(Tuple2<Optional<ReachabilityOnUsDto>, Optional<List<ReachableByCurrencyDto>>> result) {
        return ReachabilityDtoWrapper.builder()
                .reachableByOnUS(result.getT1().orElse(null))
                .reachableByCurrencies(result.getT2().orElse(null))
                .build();
    }
}

Esto completa la implementación de un nuevo endpoint que requiere algunas de las funcionalidades de alcanzabilidad.