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.