Trendyol / Jdempotent

Make your consumer, API, etc. idempotent easily.
MIT License
99 stars 24 forks source link

Using OpenApi and Spring Boot always I got the object not the String #22

Closed fintecheando closed 2 years ago

fintecheando commented 2 years ago

Hello

When adding the @IdempotentResource(cachePrefix = "CacheResource") in a class with Spring Boot and OpenApi I always fot the Object and not the body, so then the cache has two different keys and then there is not idempotency.

FIRST REQUEST fineract-server_1 | 2021-10-27 02:05:35.709 DEBUG 8 --- [nio-8443-exec-8] c.t.j.core.aspect.IdempotentAspect : ClientsApiResource.create() starting for IdempotentRequestWrapper [request=IdempotentIgnorableWrapper{nonIgnoredFields={serialPersistentFields=[Ljava.io.ObjectStreamField;@199a5ee0, CASE_INSENSITIVE_ORDER=java.lang.String$CaseInsensitiveComparator@408c8a73, coder=0, serialVersionUID=-6849794470754667710, LATIN1=0, UTF16=1, COMPACT_STRINGS=true, value=[B@4b031ff4, hash=0}}] fineract-server_1 | 2021-10-27 02:05:35.710 DEBUG 8 --- [nio-8443-exec-8] c.t.j.core.aspect.IdempotentAspect : ClientsApiResource.create() saved to cache with IdempotencyKey [keyValue=ClientsApiResource.create-d379af615329196d4f526ff2aa557] fineract-server_1 | 2021-10-27 02:05:36.652 DEBUG 8 --- [nio-8443-exec-8] c.t.j.core.aspect.IdempotentAspect : ClientsApiResource.create() ended for IdempotentRequestWrapper [request=IdempotentIgnorableWrapper{nonIgnoredFields={serialPersistentFields=[Ljava.io.ObjectStreamField;@199a5ee0, CASE_INSENSITIVE_ORDER=java.lang.String$CaseInsensitiveComparator@408c8a73, coder=0, serialVersionUID=-6849794470754667710, LATIN1=0, UTF16=1, COMPACT_STRINGS=true, value=[B@4b031ff4, hash=0}}]

SECOND REQUEST fineract-server_1 | 2021-10-27 02:05:36.652 DEBUG 8 --- [nio-8443-exec-8] c.t.j.core.aspect.IdempotentAspect : ClientsApiResource.create() ended for IdempotentRequestWrapper [request=IdempotentIgnorableWrapper{nonIgnoredFields={serialPersistentFields=[Ljava.io.ObjectStreamField;@199a5ee0, CASE_INSENSITIVE_ORDER=java.lang.String$CaseInsensitiveComparator@408c8a73, coder=0, serialVersionUID=-6849794470754667710, LATIN1=0, UTF16=1, COMPACT_STRINGS=true, value=[B@4b031ff4, hash=0}}] fineract-server_1 | 2021-10-27 02:05:52.284 DEBUG 8 --- [nio-8443-exec-9] c.t.j.core.aspect.IdempotentAspect : ClientsApiResource.create() starting for IdempotentRequestWrapper [request=IdempotentIgnorableWrapper{nonIgnoredFields={serialPersistentFields=[Ljava.io.ObjectStreamField;@199a5ee0, CASE_INSENSITIVE_ORDER=java.lang.String$CaseInsensitiveComparator@408c8a73, coder=0, serialVersionUID=-6849794470754667710, LATIN1=0, UTF16=1, COMPACT_STRINGS=true, value=[B@6ed5a12a, hash=0}}] fineract-server_1 | 2021-10-27 02:05:52.287 DEBUG 8 --- [nio-8443-exec-9] c.t.j.core.aspect.IdempotentAspect : ClientsApiResource.create() saved to cache with IdempotencyKey [keyValue=ClientsApiResource.create-5319fba2c9d479e238c199b671a14] fineract-server_1 | 2021-10-27 02:05:52.315 WARN 8 --- [nio-8443-exec-9] o.d.jdbc.internal.mysql.MySQLProtocol : Could not execute query org.drizzle.jdbc.internal.common.query.DrizzleParameterizedQuery@41c19dfc: Duplicate entry '786YYH72' for key 'external_id' fineract-server_1 | 2021-10-27 02:05:52.330 DEBUG 8 --- [nio-8443-exec-9] c.t.j.core.aspect.IdempotentAspect : ClientsApiResource.create() deleted from cache with IdempotencyKey [keyValue=ClientsApiResource.create-5319fba2c9d479e238c199b671a14] . Exception : {}

The Java class (Post method):

@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Create a Client", description = "Note:\n\n"
        + "1. You can enter either:firstname/middlename/lastname - for a person (middlename is optional) OR fullname - for a business or organisation (or person known by one name).\n"
        + "\n" + "2.If address is enable(enable-address=true), then additional field called address has to be passed.\n\n"
        + "Mandatory Fields: firstname and lastname OR fullname, officeId, active=true and activationDate OR active=false, if(address enabled) address\n\n"
        + "Optional Fields: groupId, externalId, accountNo, staffId, mobileNo, savingsProductId, genderId, clientTypeId, clientClassificationId")
@IdempotentResource(cachePrefix = "CacheResource")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = ClientsApiResourceSwagger.PostClientsRequest.class)))
@ApiResponses({
        @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ClientsApiResourceSwagger.PostClientsResponse.class))) })
public String create(@Parameter(hidden = true) @IdempotentRequestPayload final String apiRequestBodyAsJson) {
    logger.info(apiRequestBodyAsJson);
    final CommandWrapper commandRequest = new CommandWrapperBuilder() //
            .createClient() //
            .withJson(apiRequestBodyAsJson) //
            .build(); //

    final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);

    return this.toApiJsonSerializer.serialize(result);
}
memojja commented 2 years ago

Hello, could you share a sample source code? Let's examine.

IOhacker commented 2 years ago

Yes,

It is another Open Source Project, which I am contributor, and we are exploring to use the Idempotency Libraries

This is the fork where I have submitted my changes and it is the complete class, the line where I am using Jdempotent is the 240, in the POST

https://github.com/IOhacker/fineract/blob/idempotency/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResource.java

Best regards

Victor

memojja commented 2 years ago

Hi,

I think the data ordering is different in the json that comes in string as apiRequestBodyAsJson.

For example, one of them is as follows

{
   "email":"blabla@trendyol.com",
   "password":"blabla",
   "jobDesc": "developer"
}

another like this. And the hash is different.

{
   "jobDesc": "developer",
   "email":"blabla@trendyol.com",
   "password":"blabla"
}

But I think it's ok if you get a serialized form instead of string

final CommandWrapper commandWrapper

instead of

final String apiRequestBodyAsJson

memojja commented 2 years ago

Could you also share the curl requests you tried?

IOhacker commented 2 years ago

This is the CURL

curl --location --request POST 'https://localhost:8443/fineract-provider/api/v1/clients' \ --header 'Fineract-Platform-TenantId: default' \ --header 'Authorization: Basic bWlmb3M6cGFzc3dvcmQ=' \ --header 'Content-Type: application/json' \ --data-raw '{ "officeId": 1, "firstname": "Petra", "lastname": "Yton", "externalId": "786YYH72", "dateFormat": "dd MMMM yyyy", "locale": "en", "active": true, "activationDate": "26 October 2021", "submittedOnDate":"26 October 2021", "savingsProductId" : 1 }'

IOhacker commented 2 years ago

Hello,

This commit shows the changes that I have done (I will try to get the serialized form)

https://github.com/IOhacker/fineract/commit/18b7095c25876926fc11c9da787362af91cf74ff

They are in a branch (develop)

This is the log https://paste.apache.org/oyn4q

This is another CURL

curl --location --request POST 'https://localhost:8443/fineract-provider/api/v1/savingsproducts' \ --header 'Fineract-Platform-TenantId: default' \ --header 'Authorization: Basic bWlmb3M6cGFzc3dvcmQ=' \ --header 'Content-Type: application/json' \ --data-raw '{ "currencyCode": "USD", "digitsAfterDecimal": 2, "interestCompoundingPeriodType": 1, "interestPostingPeriodType": 4, "interestCalculationType": 1, "interestCalculationDaysInYearType": 365, "accountingRule": "1", "name": "DITO", "shortName": "DITO", "inMultiplesOf": "1", "nominalAnnualInterestRate": 0, "paymentChannelToFundSourceMappings": [], "feeToIncomeAccountMappings": [], "penaltyToIncomeAccountMappings": [], "charges": [], "locale": "en" }'

memojja commented 2 years ago

waiting for your reply for serialized form

IOhacker commented 2 years ago

Hello, I will close the issue, because I have prepared another approach which is a better alternative for the use case that we are trying to solve, I have sent the PR as reference.