Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request for GenericTypeIndicator Support in spring-cloud-gcp-starter-data-firestore for Handling Generic Types #2784

Open
gustavostz opened this issue Apr 14, 2024 · 0 comments
Labels
priority: p2 type: enhancement New feature or request

Comments

@gustavostz
Copy link

gustavostz commented Apr 14, 2024

Problem

I am implementing a generic Idempotency handling mechanism in my service using Firestore. However, I encountered a serialization/deserialization issue when dealing with generic types. The absence of GenericTypeIndicator in the Firestore integration for Spring makes it challenging to implement a clean and efficient solution for generic data handling.

Example of Current Implementation:

Domain

@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collectionName = "idempotencies")
public class Idempotency<T> {

    @DocumentId
    private String key;

    private T response;

    private LocalDateTime expiryDate;

    public Idempotency(String key, T response) {
        this.key = key;
        this.response = response;
    }
}

Service

@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class IdempotencyService {

    private final IdempotencyRepository repository;

    public <T> Mono<T> checkIdempotency(String idempotencyKey, Class<T> type) {
        return repository.findById(idempotencyKey)
                .filter(this::isIdempotencyValid)
                .map(Idempotency::getResponse)
                .cast(type)
                .doOnSuccess(response -> {
                    if (response != null) {
                        log.info("Idempotency key {} triggered, returning saved value: {}.", idempotencyKey, response);
                    } else {
                        log.info("Idempotency key {} triggered, but no response to return (No value stored or key expired).", idempotencyKey);
                    }
                })
                .doOnError(error -> log.error("Error checking idempotency for key {}: {}", idempotencyKey, error.getMessage()));
    }
    
    // ... Other generic methods

Problem

2024-04-13 23:24:12.782 [http-nio-0.0.0.0-8084-exec-2] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.RuntimeException: Could not deserialize object. Class com.myvrcloud.user.domain.Idempotency has generic type parameters, please use GenericTypeIndicator instead] with root cause 
java.lang.RuntimeException: Could not deserialize object. Class com.myvrcloud.user.domain.Idempotency has generic type parameters, please use GenericTypeIndicator instead

Proposed Solution:

I suggest adding GenericTypeIndicator or similar functionality to spring-cloud-gcp-starter-data-firestore to facilitate the handling of generic types. This feature is crucial for developing robust and type-safe applications and would significantly enhance the usability of Firestore with Spring.

Possible Workaround

Currently, I am serializing the generic type to a JSON string and deserializing it manually. While this approach works, it is cumbersome and detracts from the clean, type-safe approach facilitated by Spring Data. Something like:

public <T> Mono<T> checkIdempotency(String idempotencyKey, Class<T> type) {
        return repository.findById(idempotencyKey)
            .filter(this::isIdempotencyValid)
            .flatMap(idempotency -> {
                try {
                    T response = objectMapper.readValue(idempotency.getResponse(), type);
                    return Mono.just(response);
                } catch (Exception e) {
                    log.error("Error deserializing response", e);
                    return Mono.error(e);
                }
            });
    }

Or something like:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collectionName = "idempotencies")
public class Idempotency {

    @DocumentId
    private String key;

    @Exclude
    @Getter(onMethod_ = @Exclude)
    @Setter(onMethod_ = @Exclude)
    private String responseJson;

    private LocalDateTime expiryDate;

    @Exclude
    private Object response;

    public Idempotency(String key, Object response) {
        this.key = key;
        this.setResponse(response);
    }

    @PropertyName("response")
    public void setResponse(Object response) {
        try {
            this.responseJson = JsonUtil.getMapper().writeValueAsString(response);
            this.response = response;
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Failed to serialize response", e);
        }
    }

    @PropertyName("response")
    public <T> T getResponse(Class<T> type) {
        try {
            return new ObjectMapper().readValue(this.responseJson, type);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Failed to deserialize response", e);
        }
    }
}

// checkIdempotency method in the Service will need to use the method...

repository.findById(idempotencyKey)
                .filter(this::isIdempotencyValid)
                .map(idempotency -> idempotency.getResponse(type))
                //...

Conclusion

The inclusion of generic type handling in Firestore's Spring Data integration would align it with other parts of the Spring ecosystem, which already support generics effectively. This enhancement would greatly simplify the development process and improve code quality (and avoid the need for "workarounds").

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
priority: p2 type: enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants