Skip to content

Commit

Permalink
feat: [PagoPa-1797] paging and caching (#70)
Browse files Browse the repository at this point in the history
* [PAGOPA-1797] paging and caching: first DRAFT impl

* [PAGOPA-1797] paging and caching: REDIS impl

---------

Co-authored-by: aacitelli <aacitelli@PDD-NB-0205.dgsgroup.it>
  • Loading branch information
alessio-acitelli and aacitelli authored Jun 13, 2024
1 parent 1c8a927 commit c6aeaf1
Show file tree
Hide file tree
Showing 25 changed files with 505 additions and 11 deletions.
4 changes: 4 additions & 0 deletions helm/values-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ microservice-chart:
GENERATE_PDF_RETRY_MAX_DELAY: "10000"
GENERATE_PDF_RETRY_MAX_ATTEMPTS: "1"
CONNECTION_TIMEOUT: "10000"
REDIS_PORT: "6380"
REDIS_TTL: "5" # 5 minutes
OTEL_SERVICE_NAME: "pagopabizeventsservice"
OTEL_RESOURCE_ATTRIBUTES: "service.name=pagopareceiptspdfserviceotl,deployment.environment=dev"
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317"
Expand All @@ -99,6 +101,8 @@ microservice-chart:
COSMOS_DB_PRIMARY_KEY: 'cosmos-d-biz-key'
PDF_RECEIPT_SUBSCRIPTION_KEY: "bizevent-d-receiptpdfservice-subscription-key"
PDF_GENERATE_RECEIPT_SUBSCRIPTION_KEY: "bizevent-d-generatepdfservice-subscription-key"
REDIS_PWD: 'redis-password'
REDIS_HOST: 'redis-hostname'
OTEL_EXPORTER_OTLP_HEADERS: 'elastic-otl-secret-token'
keyvault:
name: "pagopa-d-bizevents-kv"
Expand Down
4 changes: 4 additions & 0 deletions helm/values-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ microservice-chart:
GENERATE_PDF_RETRY_MAX_DELAY: "10000"
GENERATE_PDF_RETRY_MAX_ATTEMPTS: "1"
CONNECTION_TIMEOUT: "10000"
REDIS_PORT: "6380"
REDIS_TTL: "20" # 20 minutes
OTEL_SERVICE_NAME: "pagopabizeventsservice"
OTEL_RESOURCE_ATTRIBUTES: "service.name=pagopareceiptspdfserviceotl,deployment.environment=prod"
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317"
Expand All @@ -99,6 +101,8 @@ microservice-chart:
COSMOS_DB_PRIMARY_KEY: 'cosmos-p-biz-key'
PDF_RECEIPT_SUBSCRIPTION_KEY: "bizevent-p-receiptpdfservice-subscription-key"
PDF_GENERATE_RECEIPT_SUBSCRIPTION_KEY: "bizevent-p-generatepdfservice-subscription-key"
REDIS_PWD: 'redis-password'
REDIS_HOST: 'redis-hostname'
OTEL_EXPORTER_OTLP_HEADERS: 'elastic-otl-secret-token'
keyvault:
name: "pagopa-p-bizevents-kv"
Expand Down
4 changes: 4 additions & 0 deletions helm/values-uat.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ microservice-chart:
GENERATE_PDF_RETRY_MAX_DELAY: "10000"
GENERATE_PDF_RETRY_MAX_ATTEMPTS: "1"
CONNECTION_TIMEOUT: "10000"
REDIS_PORT: "6380"
REDIS_TTL: "20" # 20 minutes
OTEL_SERVICE_NAME: "pagopabizeventsservice"
OTEL_RESOURCE_ATTRIBUTES: "service.name=pagopareceiptspdfserviceotl,deployment.environment=uat"
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317"
Expand All @@ -99,6 +101,8 @@ microservice-chart:
COSMOS_DB_PRIMARY_KEY: 'cosmos-u-biz-key'
PDF_RECEIPT_SUBSCRIPTION_KEY: "bizevent-u-receiptpdfservice-subscription-key"
PDF_GENERATE_RECEIPT_SUBSCRIPTION_KEY: "bizevent-u-generatepdfservice-subscription-key"
REDIS_PWD: 'redis-password'
REDIS_HOST: 'redis-hostname'
OTEL_EXPORTER_OTLP_HEADERS: 'elastic-otl-secret-token'
keyvault:
name: "pagopa-u-bizevents-kv"
Expand Down
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- AZURE -->
<dependency>
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/it/gov/pagopa/bizeventsservice/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
public class Application {

public static void main(String[] args) {
// to avoid java.lang.ClassCastException for objects fetched from the REDIS cache
System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(Application.class, args);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package it.gov.pagopa.bizeventsservice.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

@Value("${spring.redis.host}")
private String redisHost;

@Value("${spring.redis.port}")
private int redisPort;

@Value("${spring.redis.pwd}")
private String redisPwd;

@Bean
public ObjectMapper objectMapper() {
final var objectMapper = new ObjectMapper().findAndRegisterModules();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
return objectMapper;
}

@Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisConfiguration =
new RedisStandaloneConfiguration(redisHost, redisPort);
redisConfiguration.setPassword(redisPwd);
LettuceClientConfiguration lettuceConfig =
LettuceClientConfiguration.builder().useSsl().build();
return new LettuceConnectionFactory(redisConfiguration, lettuceConfig);
}

@Bean
@Qualifier("object")
public RedisTemplate<String, byte[]> redisObjectTemplate(
final LettuceConnectionFactory connectionFactory, ObjectMapper objectMapper) {
RedisTemplate<String, byte[]> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(connectionFactory);
return template;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public interface ITransactionController {
String X_CONTINUATION_TOKEN = "x-continuation-token";
String X_FISCAL_CODE = "x-fiscal-code";
String PAGE_SIZE = "size";
String PAGE_NUMBER = "page";

/**
* recovers biz-event data for the transaction list
Expand All @@ -60,6 +61,30 @@ ResponseEntity<TransactionListWrapResponse> getTransactionList(
@RequestParam(name = PAGE_SIZE, required = false, defaultValue = "10") Integer size

);

/**
* recovers biz-event data for the transaction list
*
* @param fiscalCode tokenized user fiscal code
* @param page optional parameter defining page number, default to 0 (first page)
* @param size optional parameter defining page size, defaults to 10
* @return the transaction list
*/
@GetMapping(value = "/cached", produces = MediaType.APPLICATION_JSON_VALUE)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Obtained transaction list.",
content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(name = "TransactionListWrapResponse", implementation = TransactionListWrapResponse.class))),
@ApiResponse(responseCode = "401", description = "Wrong or missing function key.", content = @Content(schema = @Schema())),
@ApiResponse(responseCode = "404", description = "Not found the transaction.", content = @Content(schema = @Schema(implementation = ProblemJson.class))),
@ApiResponse(responseCode = "429", description = "Too many requests.", content = @Content(schema = @Schema())),
@ApiResponse(responseCode = "500", description = "Service unavailable.", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ProblemJson.class)))})
@Operation(summary = "Retrieve the paged transaction list from biz events.", security = {
@SecurityRequirement(name = "ApiKey")}, operationId = "getTransactionList")
ResponseEntity<TransactionListWrapResponse> getCachedTransactionList(
@RequestHeader(name = X_FISCAL_CODE) String fiscalCode,
@RequestParam(name = PAGE_NUMBER, required = false, defaultValue = "0") Integer page,
@RequestParam(name = PAGE_SIZE, required = false, defaultValue = "10") Integer size
);

@Operation(summary = "Retrieve the transaction details given its id.", security = {
@SecurityRequirement(name = "ApiKey")}, operationId = "getTransactionDetails")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ public ResponseEntity<TransactionListWrapResponse> getTransactionList(
.header(X_CONTINUATION_TOKEN, transactionListResponse.getContinuationToken())
.body(TransactionListWrapResponse.builder().transactions(transactionListResponse.getTransactionList()).build());
}

@Override
public ResponseEntity<TransactionListWrapResponse> getCachedTransactionList(String fiscalCode,
Integer page, Integer size) {
TransactionListResponse transactionListResponse = transactionService.getCachedTransactionList(fiscalCode, page, size);
return ResponseEntity.ok()
.body(TransactionListWrapResponse.builder()
.transactions(transactionListResponse.getTransactionList())
.pageInfo(transactionListResponse.getPageInfo())
.build());
}

@Override
public ResponseEntity<TransactionDetailResponse> getTransactionDetails(String fiscalCode, String eventReference) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package it.gov.pagopa.bizeventsservice.entity.view;

import java.io.Serializable;

import com.azure.spring.data.cosmos.core.mapping.Container;
import com.azure.spring.data.cosmos.core.mapping.GeneratedValue;
import com.azure.spring.data.cosmos.core.mapping.PartitionKey;
Expand All @@ -14,8 +16,13 @@
@Getter
@Setter
@Builder
public class BizEventsViewUser {
@GeneratedValue
public class BizEventsViewUser implements Serializable {
/**
*
*/
private static final long serialVersionUID = -4997399615775767480L;

@GeneratedValue
private String id;
@PartitionKey
private String taxCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,6 @@ public static TransactionListItem convertTransactionListItem(BizEventsViewUser v
totalAmount.updateAndGet(v -> v.add(amountExtracted));
}

// check if the cart contains an item in which the user is a debtor
boolean userHasDebtorItemInTheCart = listOfCartViews.stream().anyMatch(x -> viewUser.getTaxCode().equals(x.getDebtor().getTaxCode()));

return TransactionListItem.builder()
.transactionId(viewUser.getTransactionId())
.payeeName(listOfCartViews.size() > 1 ? payeeCartName : listOfCartViews.get(0).getPayee().getName())
Expand All @@ -98,7 +95,7 @@ public static TransactionListItem convertTransactionListItem(BizEventsViewUser v
.transactionDate(dateFormatZoned(viewUser.getTransactionDate()))
.isCart(listOfCartViews.size() > 1)
.isPayer(BooleanUtils.isTrue(viewUser.getIsPayer()))
.isDebtor(userHasDebtorItemInTheCart ? userHasDebtorItemInTheCart: BooleanUtils.isTrue(viewUser.getIsDebtor()))
.isDebtor(BooleanUtils.isTrue(viewUser.getIsDebtor()))
.build();
}

Expand Down
44 changes: 44 additions & 0 deletions src/main/java/it/gov/pagopa/bizeventsservice/model/PageInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package it.gov.pagopa.bizeventsservice.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import javax.validation.constraints.Positive;
import javax.validation.constraints.PositiveOrZero;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class PageInfo {

@JsonProperty("page")
@Schema(description = "Page number", required = true)
@PositiveOrZero
Integer page;

@JsonProperty("limit")
@Schema(description = "Required number of items per page", required = true)
@Positive
Integer limit;

@JsonProperty("items_found")
@Schema(description = "Number of items found. (The last page may have fewer elements than required)", required = true)
@PositiveOrZero
Integer itemsFound;

@JsonProperty("total_pages")
@Schema(description = "Total number of pages", required = true)
@PositiveOrZero
Integer totalPages;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@

import java.util.List;

import it.gov.pagopa.bizeventsservice.model.PageInfo;

@Builder
@Getter
public class TransactionListResponse {
private List<TransactionListItem> transactionList;
private String continuationToken;
private PageInfo pageInfo;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@

import java.util.List;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

import it.gov.pagopa.bizeventsservice.model.PageInfo;

@Builder
@Getter
@JsonInclude(Include.NON_NULL)
public class TransactionListWrapResponse {
private List<TransactionListItem> transactions;
@JsonProperty("page_info")
private PageInfo pageInfo;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
public interface BizEventsViewUserRepository extends CosmosRepository<BizEventsViewUser, String> {
@Query("select * from c where c.taxCode = @taxCode and c.hidden = false")
Page<BizEventsViewUser> getBizEventsViewUserByTaxCode(@Param("taxCode") String taxCode, Pageable pageable);

@Query("select * from c where c.taxCode = @taxCode and c.hidden = false")
List<BizEventsViewUser> getBizEventsViewUserByTaxCode(@Param("taxCode") String taxCode);

@Query("select * from c where c.transactionId=@transactionId and c.taxCode = @fiscalCode and c.hidden = false")
List<BizEventsViewUser> getBizEventsViewUserByTaxCodeAndTransactionId(String fiscalCode, String transactionId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package it.gov.pagopa.bizeventsservice.repository.redis;

import java.time.Duration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;


@Component
public class RedisRepository {

@Autowired
@Qualifier("object")
private RedisTemplate<String, byte[]> redisTemplateObj;

public void save(String key, byte[] value, long ttl) {
redisTemplateObj.opsForValue().set(key, value, Duration.ofMinutes(ttl));
}

public byte[] get(String key) {
return redisTemplateObj.opsForValue().get(key);
}

public void remove(String key) {
redisTemplateObj.delete(key);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public interface ITransactionService {
* @return transaction list
*/
TransactionListResponse getTransactionList(String fiscalCode, String continuationToken, Integer size);
TransactionListResponse getCachedTransactionList(String fiscalCode, Integer page, Integer size);
TransactionDetailResponse getTransactionDetails(String fiscalCode, String transactionId);

void disableTransaction(String fiscalCode, String transactionId);
Expand Down
Loading

0 comments on commit c6aeaf1

Please sign in to comment.