Create Settings
The following is an example of how to add a setting, in this case a CsmAgent Setting that will be managed by the platform. You need to configure a domain project and a repository project
Domain Project Setup
In order to add a setting to be managed by the Dynamic Processing Settings Platform you need to create the following:
-
Setting Definition
-
Domain Object
-
Search Fields for the setting
Setting Definition
Specifies how to calculate the logical unique key for the setting and associates all the other components (domain object and search fields) to the setting concept
@Bean
SettingDefinition csmAgentSettingDefinition(final Notifier systemEventSender) {
return SettingDefinition.<CsmAgent>builder()
.name("csmagent")
.clazz(CsmAgent.class)
.idFunction(setting -> setting.getProcessingEntity() + "-" + setting.getPayload().getCsmAgentId())
.approvalFunction((requiresApproval, persistanceId, inputSetting) -> CompletableFuture.completedStage(requiresApproval))
.searchableFields(CsmAgentSearchableFields.class)
.notificationFunction(systemEventSender::notify)
.build();
}
Domain Object
This will be the payload of a setting object and should contain all the relevant attributes for the setting you wish to define
@Data
@Builder(toBuilder = true)
public class CsmAgent {
@NotNull
private String csmAgentId;
private String csmAgentBic;
@Size(max = 70)
private String csmAgentName;
@NotNull
@Size(max = 35)
private String csmAgentType;
@NotNull
@Size(max = 15)
private String csmParticipantIdentifierType;
@NotNull
@Size(max = 35)
private String csmAgentConnector;
@Size(max = 70)
private String csmAgentConnectorAddress;
@Size(min=1)
@Valid
private List<CsmAgentMessageStandard> csmAgentMessageStandards;
private Boolean onUsCSM;
private Boolean higherParticipantLimitNotAllowed;
private Boolean instantPayments;
@Data
@Builder
public static class CsmAgentMessageStandard {
@NotNull
@Size(max = 35)
private String messageStandard;
@NotNull
@Size(max = 35)
private String messageStandardVersion;
@NotNull
private Instant activeFrom;
}
public boolean isHigherParticipantLimitNotAllowed() {
return BooleanUtils.isTrue(higherParticipantLimitNotAllowed);
}
public boolean isOnUsCSM() {
return BooleanUtils.isTrue(onUsCSM);
}
public boolean isInstantPayments() {
return BooleanUtils.isTrue(instantPayments);
}
}
Setting class:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Setting <T> implements Serializable {
private String id;
@Size(max = 15, min = 1)
private String processingEntity;
private Instant activeFromDate;
private String source;
private String status;
private int version;
private String createdBy;
private String rejectedBy;
private String approvedBy;
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "className")
private T payload;
@JsonIgnore
public boolean isActive() {
return "ACTIVE".equalsIgnoreCase(status);
}
}
Search Fields
Define the fields which are searchable on the setting, in this case the CsmAgent can be searched by CsmAgentId.
package com.iconsolutions.ipf.dynamicsettings.search;
public enum CsmAgentSettingSearchFields implements SearchField {
CSM_AGENT_ID;
@Override
public String getName() {
return this.name();
}
}
In addition to the search fields you define for the setting, all settings are searchable via CommonSearchFields (status, processingEntity, activeFrom and source)
@Data
public class CommonSearchableFields implements SearchableFields {
private String status;
private String processingEntity;
private Instant activeFrom;
private List<String> idList;
@Pattern(regexp = "import|manual", flags = Pattern.Flag.CASE_INSENSITIVE)
private String source;
public CommonSearchableFields populateFromRequest(ServerRequest serverRequest) {
CommonSearchableFields commonSearchableFields = newInstance();
serverRequest.queryParam("status").ifPresent(commonSearchableFields::setStatus);
serverRequest.queryParam("processingEntity").ifPresent(commonSearchableFields::setProcessingEntity);
serverRequest.queryParam("source").ifPresent(commonSearchableFields::setSource);
serverRequest.queryParam("activeFrom").ifPresent(activeFrom1 -> commonSearchableFields.setActiveFrom(Instant.parse(activeFrom1)));
return commonSearchableFields;
}
public CommonSearchableFields newInstance() {
return new CommonSearchableFields();
}
@Override
public List<Criterion> criteria() {
final List<Criterion> criteria = new ArrayList<>();
if (status != null) {
criteria.add(Criterion.equalTo(SettingSearchFields.STATUS, status));
} else {
criteria.add(Criterion.notEqualTo(SettingSearchFields.STATUS, "INITIAL"));
}
if (activeFrom != null) {
criteria.add(Criterion.gte(SettingSearchFields.ACTIVE_FROM, activeFrom));
}
if (source != null) {
criteria.add(Criterion.equalTo(SettingSearchFields.SOURCE, source));
}
if (processingEntity != null) {
criteria.add(Criterion.equalTo(SettingSearchFields.PROCESSING_ENTITY, processingEntity));
}
if(idList != null) {
criteria.add(Criterion.in(SettingSearchFields.ID, idList));
}
return criteria;
}
}
The below tells the framework how to extract the search fields from the requests received
@Data
public class CsmAgentSearchableFields extends CommonSearchableFields {
private String csmAgentId;
@Override
public CsmAgentSearchableFields populateFromRequest(ServerRequest serverRequest) {
CsmAgentSearchableFields searchableFields = (CsmAgentSearchableFields) super.populateFromRequest(serverRequest);
serverRequest.queryParam("csmAgentId").ifPresent(searchableFields::setCsmAgentId);
return searchableFields;
}
@Override
public CsmAgentSearchableFields newInstance() {
return new CsmAgentSearchableFields();
}
@Override
public List<Criterion> criteria() {
final List<Criterion> criteria = new ArrayList<>(super.criteria());
if (csmAgentId != null) {
criteria.add(equalTo(CsmAgentSettingSearchFields.CSM_AGENT_ID, csmAgentId));
}
return criteria;
}
}
You also need to update the search fields map which specifies the path to the searchable field from the perspective of a setting
@PostConstruct
void updateSearchFieldsMap() {
settingSearchFieldsMapper.putMapping(CsmAgentSettingSearchFields.CSM_AGENT_ID.getName(), "payload.csmAgentId");
}
Repository Project Setup
Additionally, the following read side infrastructure needs to be defined:
-
Repository
-
ModelEntity
-
ModelEntityProvider
-
IndexInitialiser
Repository
Repository, which extends ReactiveCRUDRepository and exposes the query functionality of the setting stored in the database
public interface CsmAgentSettingsRepository extends SettingRepository<CsmAgentSettings> {
String CSMAGENT = "csmagent-";
Flux<CsmAgentSettings> findAll(Sort sort);
@Override
default boolean supports(String id) {
return id.toLowerCase().contains(CSMAGENT);
}
}
ModelEntity
ModelEntity, defines how the setting will be represented in the DB and also defines how the payload for the settings is created/updated
@Document(collection = "settings-csm-agent")
@Data
public class CsmAgentSettings extends MongoSettingReadModelEntity<CsmAgent> {
@Override
protected Supplier<CsmAgent> payloadCreator() {
return () -> CsmAgent.builder().build();
}
@Override
protected BiFunction<Event, CsmAgent, CsmAgent> payloadUpdater() {
return (event, csmAgent) -> csmAgent;
}
}
ModelEntityProvider
ModelEntityProvider, is responsible for creating the appropriate ModelEntity, based on the identifier that is input
@Component
public class CsmAgentMongoSettingModelEntityProvider implements MongoSettingModelEntityProvider {
// "-" suffix added to avoid partial match e.g. csmagent matching csmagentcurrency
private static final String CSMAGENT = "csmagent-";
@Override
public MongoSettingReadModelEntity provide() {
return new CsmAgentSettings();
}
@Override
public Class<? extends MongoSettingReadModelEntity> getEntityClazz() {
return CsmAgentSettings.class;
}
@Override
public boolean supports(String id) {
return id.toLowerCase().contains(CSMAGENT);
}
}
IndexInitialiser
Index Initialiser, is responsible for creating indexes on the collection
@Slf4j
@AllArgsConstructor
public class CsmAgentMongoSettingRecordIndexInitialiser {
private static final String STATUS = "status";
private static final String PROCESSING_ENTITY = "processingEntity";
private static final String PAYLOAD_CSM_AGENT_ID = "payload.csmAgentId";
private static final String COLLECTION_NAME = "CsmAgentSettings";
private final ReactiveMongoTemplate reactiveMongoTemplate;
private final RepositoryRetryProvider repositoryRetryProvider;
@EventListener(ContextRefreshedEvent.class)
public void initialise() {
log.info("creating indexes");
final ReactiveIndexOperations indexOperations = reactiveMongoTemplate
.indexOps(CsmAgentSettings.class);
createIndex(indexOperations, STATUS, COLLECTION_NAME, repositoryRetryProvider);
createIndex(indexOperations, PROCESSING_ENTITY, COLLECTION_NAME, repositoryRetryProvider);
createIndex(indexOperations, PAYLOAD_CSM_AGENT_ID, COLLECTION_NAME, repositoryRetryProvider);
}
}