Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Changelog
All notable changes to this project will be documented in this file.

## [9.10.0]
### Changed
- Investment service
- avoid redundant API calls by checking for unchanged data before patch/update for markets, market special days, asset category types, asset categories, assets and currencies
- improve structured logging across investment upsert operations and renamed few methods

## [9.8.0]
### Changed
- Create investment data based on exists entries in system
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@
import java.util.Map;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.core.io.Resource;

/**
* Lightweight projection of {@link com.backbase.investment.api.service.v1.model.Asset} that keeps the DTO immutable
* while providing helpers to translate to/from the generated model.
*/
@Setter
@Getter
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@Builder(toBuilder = true)
@EqualsAndHashCode(exclude = {"uuid", "logo", "logoFile", "categories"})
public class Asset implements AssetKey {

private UUID uuid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.core.io.Resource;

@Setter
@Getter
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(exclude = {"uuid", "image", "imageResource"})
public class AssetCategoryEntry {

private UUID uuid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public Mono<InvestmentAssetsTask> executeTask(InvestmentAssetsTask streamTask) {
.flatMap(this::upsertMarketSpecialDays)
.flatMap(this::upsertAssetCategoryTypes)
.flatMap(this::upsertAssetCategories)
.flatMap(this::createAssets)
.flatMap(this::upsertAssets)
.flatMap(this::upsertPrices)
.flatMap(this::createIntradayPrices)
.doOnSuccess(completedTask -> log.info(
Expand Down Expand Up @@ -116,7 +116,7 @@ public Mono<InvestmentAssetsTask> rollBack(InvestmentAssetsTask streamTask) {
private Mono<InvestmentAssetsTask> upsertCurrencies(InvestmentAssetsTask investmentTask) {
InvestmentAssetData investmentData = investmentTask.getData();
int currencyCount = investmentData.getCurrencies() != null ? investmentData.getCurrencies().size() : 0;
log.info("Starting investment currency creation: taskId={}, currencies={}",
log.info("Starting investment currency creation: taskId={}, currencyCount={}",
investmentTask.getId(), currencyCount);
// Log the start of market creation and set task state to IN_PROGRESS
investmentTask.info(INVESTMENT, OP_CREATE, null, investmentTask.getName(), investmentTask.getId(),
Expand All @@ -136,12 +136,12 @@ private Mono<InvestmentAssetsTask> upsertCurrencies(InvestmentAssetsTask investm
investmentTask.getId(),
OP_UPSERT + " " + currencies.size() + " Investment Currencies");
investmentTask.setState(State.COMPLETED);
log.info("Successfully processed all currencies: taskId={}, marketCount={}",
log.info("Successfully processed all currencies: taskId={}, currencyCount={}",
investmentTask.getId(), currencies.size());
return investmentTask;
})
.doOnError(throwable -> {
log.error("Failed to create/upsert investment currencies: taskId={}, marketCount={}",
log.error("Failed to create/upsert investment currencies: taskId={}, currencyCount={}",
investmentTask.getId(), currencyCount, throwable);
investmentTask.error(INVESTMENT, OP_CREATE, RESULT_FAILED, investmentTask.getName(),
investmentTask.getId(),
Expand Down Expand Up @@ -363,13 +363,13 @@ public Mono<InvestmentAssetsTask> upsertAssetCategoryTypes(InvestmentAssetsTask
}

/**
* Creates investment assets by invoking the asset universe service for each asset in the task data. Updates the
* Upserts investment assets by invoking the asset universe service for each asset in the task data. Updates the
* task state and logs progress for observability.
*
* @param investmentTask the investment task containing asset data
* @return Mono<InvestmentTask> with updated assets and state
*/
public Mono<InvestmentAssetsTask> createAssets(InvestmentAssetsTask investmentTask) {
public Mono<InvestmentAssetsTask> upsertAssets(InvestmentAssetsTask investmentTask) {
InvestmentAssetData investmentData = investmentTask.getData();
int assetCount = investmentData.getAssets() != null ? investmentData.getAssets().size() : 0;

Expand All @@ -387,7 +387,7 @@ public Mono<InvestmentAssetsTask> createAssets(InvestmentAssetsTask investmentTa
return Mono.just(investmentTask);
}
// Process each asset: create or get from asset universe service
return assetUniverseService.createAssets(investmentData.getAssets())
return assetUniverseService.upsertAssets(investmentData.getAssets())
.collectList()
.doOnSuccess(assets -> {
investmentTask.setAssets(assets);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.backbase.stream.investment.service;

import com.backbase.investment.api.service.v1.model.AssetCategory;
import com.backbase.investment.api.service.v1.model.AssetCategoryTypeRequest;
import com.backbase.investment.api.service.v1.model.Market;
import com.backbase.investment.api.service.v1.model.MarketRequest;
import com.backbase.investment.api.service.v1.model.MarketSpecialDay;
import com.backbase.investment.api.service.v1.model.MarketSpecialDayRequest;
import com.backbase.stream.investment.Asset;
import java.util.ArrayList;
import com.backbase.stream.investment.model.AssetCategoryEntry;
import java.util.List;
import java.util.Objects;
import org.mapstruct.Mapper;
Expand All @@ -12,13 +17,71 @@
@Mapper
public interface AssetMapper {

/**
* Maps a v1 API {@link com.backbase.investment.api.service.v1.model.Asset} response to the
* stream {@link Asset} model. {@code logo} is explicitly ignored because the API model holds a
* {@link java.net.URI} (a full signed URL) while the stream model holds a {@code String}
* filename — the type mismatch prevents automatic mapping. Logo change detection is handled
* separately in {@code isLogoUnchanged()} via a URI {@code contains} check.
*/
@Mapping(target = "categories", source = "categories", qualifiedByName = "mapCategories")
@Mapping(target = "logo", ignore = true)
Asset map(com.backbase.investment.api.service.v1.model.Asset asset);

/**
* Maps an existing {@link Market} response to a {@link MarketRequest} so the two can be compared
* field-by-field using the generated {@code equals()} method to detect whether a re-run carries
* identical data and the update can be skipped.
*/
MarketRequest toMarketRequest(Market market);

/**
* Maps an existing {@link MarketSpecialDay} response to a {@link MarketSpecialDayRequest} for
* equality comparison before deciding whether to call the update API.
*
* <p>All data fields ({@code date}, {@code description}, {@code sessionStart},
* {@code sessionEnd}, {@code market}) share the same name and type in both models so MapStruct
* maps them automatically. The {@code uuid} field on {@link MarketSpecialDay} has no target in
* {@link MarketSpecialDayRequest} and is silently ignored by MapStruct.
*/
MarketSpecialDayRequest toMarketSpecialDayRequest(MarketSpecialDay marketSpecialDay);

/**
* Maps an existing {@link com.backbase.investment.api.service.v1.model.AssetCategoryType} response
* to an {@link AssetCategoryTypeRequest} for equality comparison before deciding whether to call
* the update API.
*
* <p>Both {@code code} and {@code name} share the same name and type ({@code String}) so MapStruct
* maps them automatically. The {@code uuid} field on
* {@link com.backbase.investment.api.service.v1.model.AssetCategoryType} has no target in
* {@link AssetCategoryTypeRequest} and is silently ignored by MapStruct.
*/
AssetCategoryTypeRequest toAssetCategoryTypeRequest(
com.backbase.investment.api.service.v1.model.AssetCategoryType assetCategoryType);

/**
* Maps an existing {@link AssetCategory} (v1 response model) to an {@link AssetCategoryEntry}
* for equality comparison before deciding whether to call the patch API.
*
* <p>The content fields ({@code name}, {@code code}, {@code order}, {@code type},
* {@code excerpt}, {@code description}) share the same name and type in both models and are
* mapped automatically. Two fields require explicit {@code ignore}:
* <ul>
* <li>{@code image} — type mismatch: {@code URI} on the response vs {@code String} on the
* entry. Image change detection is handled separately in
* {@code isImageUnchanged()} via a filename {@code contains} check on the URI.</li>
* <li>{@code imageResource} — no equivalent field on the response model.</li>
* </ul>
* The {@code uuid} is mapped normally (both sides are {@link java.util.UUID}) so that the
* returned entry can be used to set the uuid via {@code doOnSuccess} if needed.
*/
@Mapping(target = "imageResource", ignore = true)
@Mapping(target = "image", ignore = true)
AssetCategoryEntry toAssetCategoryEntry(AssetCategory assetCategory);

@Named("mapCategories")
default List<String> mapCategories(List<AssetCategory> categories) {
return Objects.requireNonNullElse(categories, new ArrayList<AssetCategory>())
return Objects.requireNonNullElse(categories, List.<AssetCategory>of())
.stream().map(AssetCategory::getCode).toList();
}

Expand Down
Loading
Loading