Released versions of Atoti What-If are built using a specific version of Atoti Server but will work with any non-breaking patch version of the core library.
All Atoti Server applications will contain an IDatabaseService implementation, which will be suitable for Atoti What-If in most cases. Distributed environments are a special case, where the query node requires a custom IDatabaseService with access to the data nodes.We provide a RestDistributedDatabaseService implementation for this scenario, which would typically be used as follows:
In this example, we rely on an IMultiVersionDistributedActivePivot object to retrieve the addresses of the cluster members (data nodes). These addresses will be used internally to forward simulation executions and merge the results.We also need to create an AAuthenticator supplier which will provide the security context of the thread executing a request to the RemoteDatabaseService connection. This ensures access to the data node databases is secured through built-in authentication and authorization.
Simulation persistence is enabled through the creation of an ISimulationPersistenceManager object:
@Beanpublic ISimulationPersistenceManager persistenceManager(WhatIfHibernateProperties whatIfHibernateProperties) { var configuration = new org.hibernate.cfg.Configuration(); var properties = new Properties(); properties.putAll(whatIfHibernateProperties.getPersistence()); configuration.addProperties(properties); configuration.addAnnotatedClass(DatabaseSimulationJPA.class); var sessionFactory = configuration.buildSessionFactory(); HibernateSimulationPersistenceManager<DatabaseSimulationJPA> persistenceManager = HibernateSimulationPersistenceManager.of(DatabaseSimulationJPA.class); persistenceManager.setSessionFactory(sessionFactory); return persistenceManager;}
This example uses a non-auditable Hibernate persistence manager. The following implementations of a persistence manager are available in the Atoti What-If library:
Manager
Description
HibernateSimulationPersistenceManager
Persists simulations in an SQL database, using Hibernate.
HibernateAuditablePersistenceManager
Persists simulations in an SQL database, using Hibernate. Adds audit capabilities to the persistence layer.
An instance of DatabaseSimulationEngine has to be created, and the associated DatabaseSimulationsUtils class should use the same IDatabaseService instance:
An implementation of IUniqueIdGenerator is required for the workflow to correctly assign IDs to simulation executions. The Atoti What-If library contains the IncrementalUniqueIdGenerator, providing an incremental counter starting from the Epoch second.Alternatively, we can use a custom implementation:
private static class UniqueIdGenerator implements IUniqueIdGenerator { private final AtomicLong generator = new AtomicLong(0); @Override public Long generateId() { synchronized (generator) { return generator.getAndIncrement(); } }}@Beanpublic IUniqueIdGenerator simulationIdGenerator() { return new UniqueIdGenerator();}
Definition implementations are use-case specific implementations of the IDatabaseSimulationDefinition interface, with specific required parameters, logic for creating JsonDatabaseAction objects and a method for the retrieval of before/after values for a specific instance of the definition.
The IDatabaseSimulationDefinition interface and ADatabaseSimulationDefinition abstract class
The IDatabaseSimulationDefinition interface is an extension of the core IExtendedPluginValue containing the following methods:
Method
Return type
Description
getParameters()
Map<String, String>
The implementation should return a map of the definition instance parameters as Name -> Serialized value.
toJsonDatabaseEdit()
JsonDatabaseEdit
The implementation should transform the parameters of the definition instance into a JsonDatabaseEdit to be sent to the IDatabaseService. This method will be called by the DatabaseSimulationEngine.
getDescription()
String
The implementation should return the description of the definition.
getDiffs(IDatabaseService databaseService)
List<DatabaseSimulationDiffDTO>
The implementation should provide a mechanism to retrieve a list of DatabaseSimulationDiffDTO from a database service.
The ADatabaseSimulationDefinition class is an abstract class providing a skeleton implementation of the IDatabaseSimulationDefinition interface.Abstract methods are:
Method
Return type
Description
getAllParameterNames()
Set<String>
A method for retrieving all the relevant parameter names, for instantiating the parameters Map<String, String>.
generateDatabaseActions()
JsonDatabaseAction[]
A method for generating an array of JsonDatabaseAction to use in a JsonDatabaseEdit.
Implemented or stubbed methods are:
Method
Description
getParameters()
Returns a stored Map<String, String> of parameters.
addParameter(String key, Object value)
Adds a parameter to the parameters Map<String, String>.
getParameter(String key)
Returns the String representation of the requested parameter, from the parameters Map<String, String>.
getParameter(String key, TypeReference<T> type)
Returns a deserialized parameter object from the parameters Map<String, String>, using a Jackson TypeReference. Intended to be used with Collections types.
getParameter(String key, Class<T> class)
Returns a deserialized parameter object from the parameters Map<String, String> using the class object passed in.
toJsonDatabaseEdit()
Implementation of the interface method that wraps the generateDatabaseActions() result in a JsonDatabaseEdit object.
getDiffs(IDatabaseService databaseService)
Stub implementation of the interface method which returns a list containing a DatabaseSimulationDiffDTO with all fields set to “N/A”.
As all IDatabaseSimulationDefinition instances are also IExtendedPluginValue objects that are instantiated by the Atoti Server Registry, a specialized constructor is provided in the ADatabaseSimulationDefinition class.
While completely custom implementations of IDatabaseSimulationDefinition can be created, it is strongly recommended to extend the ADatabaseSimulationDefinition class. The specialized constructor of the custom implementation can then be:
public MyCustomSimulationDefinition(Map<String, Object> parameters) { super(parameters);}
Without a constructor accepting Map<String, Object> parameters as an input, the Registry will not be able to instantiate the definitions.
Custom constructors can then be implemented to take any input and be passed through to the getParameters() method.
Creating concrete definitions for a given use-case
Starting from the ADatabaseSimulationDefinition abstract class detailed above, and taking into account the required constructors, a typical definition implementation would be:
In this example, the BranchAwareAdjustmentRequestDTO is a DTO containing all the relevant information related to the simulation parameters. The second constructor isn’t required, as the Map<String, Object> can be created externally and passed to the mandatory constructor.
The definition in this example will use the input parameters to create a single action that applies an arithmetic operation to a field in a database table:
JsonDatabaseAction for duplicating rows in a table.
Retrieves rows in the given table for the branch, adds the specified suffixes to the appropriate fields and generates a JsonDatabaseAction that inserts the resulting tuples into the table.
JsonDatabaseAction for duplicating rows in a table.
Retrieves rows in the given table for the branch, applies any defined overrides, adds the specified suffixes to the appropriate fields and generates a JsonDatabaseAction that inserts the resulting tuples into the table.
For the newUpdateDatabaseAction(String baseTable, JsonNodeCondition condition, JsonNode updateProcedure) method, we provide several implementations of the IUpdateWhereProcedureFactory interface, for use as the update procedure, through the following methods:
Generates a JsonNode update-where procedure that applies the given operation (PLUS, MINUS or SCALE) to a field, with the given value. Optionally, a list of vector indices for which the operation applies can be supplied.
Generates a JsonNode update-where procedure that selects records based on keys and fills in the remaining fields from the matched entry in the list of rows expressed as Map<String, Object>. Allows for a larger degree of flexibility than the UpdateWithFieldValuesProcedureFactory, as the field changes can be different for each key.
External data transfer is handled through DTO objects wrapping the definitions, simulations and diff objects within the simulation workflow. All DTO objects provide getters and setters for the instance parameters within the object.
If the generic endpoints available in DatabaseSimulationsRestService are not enough, a REST service handling specific simulation types can be created:
@RestController@RequestMapping(REST_API_URL_PREFIX)public class TradeRescaleRestServiceController { private static final String NAMESPACE = "services/"+ RestPrefixExtractor.REST_NAMESPACE + "/whatif"; public static final String REST_API_URL_PREFIX = "/" + NAMESPACE + "/tradescale"; @Autowired private DatabaseSimulationsWorkflow workflow; protected static final Logger LOGGER = LoggerFactory.getLogger(TradeRescaleRestServiceController.class); @PostMapping(value = "/scaleTrade") public DatabaseSimulationStatus scaleTrade(@RequestBody TradeRescaleDTO dto) { LOGGER.info("[TRADE_RESCALING] Attempting to rescale trades: {}.", dto.getTradeID()); IDatabaseSimulationDefinition definition = new TradeDuplicateAndRescaleSimulationDefinition( List.of(TradeDuplicateAndRescaleSimulationDefinition.RESCALE_SA, TradeDuplicateAndRescaleSimulationDefinition.RESCALE_IMA), dto); IDatabaseSimulation simulation = workflow.execute(new DatabaseSimulation(dto.getTitle(), definition, dto.getUserName(), dto.getBranch())); DatabaseSimulationStatus status = simulation.getStatus(); if (DatabaseSimulationStatus.FAILED.equals(status)) { LOGGER.warn("[TRADE_RESCALING] Execution failed for Simulation ID: {}", simulation.getSimulationId()); } if (DatabaseSimulationStatus.UNAUTHORISED.equals(status)) { LOGGER.warn("[TRADE_RESCALING] Execution unauthorized for user: {}", simulation.getCreatedBy()); } LOGGER.info("[TRADE_RESCALING] Successfully rescaled trades: {}.", dto.getTradeID()); return status; }}
In this example, we expose a single endpoint for the creation and execution of an IDatabaseSimulation. The service creates an IDatabaseSimulationDefinition from a custom implementation, executes it through the DatabaseSimulationsWorkflow and returns the status object.