-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
FINERACT-2110: Introduce type-safety API for commands (Note API) #3994
FINERACT-2110: Introduce type-safety API for commands (Note API) #3994
Conversation
1381b9e
to
885a2d8
Compare
private final Long commandId; | ||
private final Long resourceId; | ||
private final Long subresourceId; | ||
private final Long groupId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these IDs necessary here? This is mimicking exactly the same implementation as the current production one... in other words: if you have a command that has IDs belonging to different entities then it's not really type safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed.
Now we depend on resourceId
and subresourceId
only for representing IDs
...om/v3/commands/data/src/main/java/org/apache/fineract/v3/commands/data/JsonTypedCommand.java
Outdated
Show resolved
Hide resolved
...dler/src/main/java/org/apache/fineract/v3/commands/handler/NewTypedCommandSourceHandler.java
Outdated
Show resolved
Hide resolved
...ovider/src/main/java/org/apache/fineract/v3/commands/provider/NewCommandHandlerProvider.java
Outdated
Show resolved
Hide resolved
|
||
public interface TypedCommandProcessingService { | ||
|
||
<T> CommandProcessingResult executeCommand(CommandTypedWrapper<T> wrapper); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is the type safety for the "CommandProcessingResult"?
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@RestController |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are mixing read and write here... read related stuff is one PR, write related stuff is a separate PR... this cannot be approved, because this code is duplicated in 2 PRs!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A cleaner approach would be to have 1 controller for read and a separate one for write...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can split the controller into 2 files, but the Commands' PR depends on Queries PR (same structure, share packages, and dependencies ...etc).
If the #3915 got merged then this one can be rebased with it seamlessly (no duplication).
Am I missing something?
.../service/src/main/java/org/apache/fineract/v3/note/service/NoteWritePlatformServiceImpl.java
Outdated
Show resolved
Hide resolved
...ava/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java
Outdated
Show resolved
Hide resolved
@@ -44,6 +44,9 @@ fineract.tenant.read-only-password=${FINERACT_DEFAULT_TENANTDB_RO_PWD:} | |||
fineract.tenant.read-only-parameters=${FINERACT_DEFAULT_TENANTDB_RO_CONN_PARAMS:} | |||
fineract.tenant.read-only-name=${FINERACT_DEFAULT_TENANTDB_RO_NAME:} | |||
|
|||
# backwards compatibility for 'org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter' | |||
baseUrl=https://localhost:8443${FINERACT_SERVER_SERVLET_CONTEXT_PATH:/fineract-provider} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to use a variable for host and port... those are available already with Spring Boot (just don't remember the names). Should be used here instead of hardcoded values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to use a variable for host and port... those are available already with Spring Boot (just don't remember the names). Should be used here instead of hardcoded values.
How to use spring built-in vars?
I tried this but didn't work:
# FINERACT-914
server.forward-headers-strategy=framework
server.port=${FINERACT_SERVER_PORT:8443}
server.address=${FINERACT_SERVER_ADDRESS:localhost}
server.servlet.context-path=${FINERACT_SERVER_SERVLET_CONTEXT_PATH:/fineract-provider}
server.compression.enabled=${FINERACT_SERVER_COMPRESSION_ENABLED:true}
# backwards compatibility for 'org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter
baseUrl=https://${server.address}:${server.port}${server.servlet.context-path}
and the checks failed as following:
Run curl -f -k --retry 10 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
curl: (35) error:0A000[12](https://github.com/Zeyad2003/fineract/actions/runs/10129914159/job/28010527803#step:8:13)6:SSL routines::unexpected eof while reading
Error: Process completed with exit code 35.
any thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This approach has passed the checks:
server.forward-headers-strategy=framework
server.port=${FINERACT_SERVER_PORT:8443}
server.domain=${FINERACT_SERVER_DOMAIN:localhost}
server.servlet.context-path=${FINERACT_SERVER_SERVLET_CONTEXT_PATH:/fineract-provider}
server.compression.enabled=${FINERACT_SERVER_COMPRESSION_ENABLED:true}
# backwards compatibility for 'org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter'
baseUrl=https://${server.domain}:${server.port}${server.servlet.context-path}
It appears that the server.address
property causes some issues. The exact reason is ambiguous to me.
026a952
to
00e499a
Compare
return new CommandWrapper(officeId, groupId, clientId, loanId, savingsId, actionName, entityName, entityId, | ||
subentityId, href, json, transactionId, productId, null, creditBureauId, organisationCreditBureauId, | ||
jobName, idempotencyKey); | ||
return new CommandWrapper(null, null, null, null, null, actionName, entityName, entityId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No... this whole function is written in a way that goes against all best practices of dependency injection; "ObjectMapper" should be re-used/injected (with all the global configurations)... here we create it with default configurations over and over again... but even without that: this function is acts like copy function... how many times do we use this? My bet is again: once... this programming style is obscuring more things than it saves or helps...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just temporary. Not implemented there to remain.
Actually, I'm breaking some rules to get things to work first.
Then it can be improved or even removed.
@@ -57,7 +47,7 @@ public JsonCommand getJsonCommand() { | |||
} | |||
|
|||
return new JsonCommand(null, json, null, null, entityName, resourceId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a "CommandWrapper" and a "JsonCommand"... why? Can't we just express what we need in one class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I've thought about that, there's no major differences. (just action & entity names).
Thanks for mentioning it!
} | ||
|
||
return resourceDetails.toBuilder().entityName(resourceNameForPermissions).build(); | ||
String resourceNameForPermissions = switch (type) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without even reading more details: very likely the wrong place for ANYTHING permission related! Note: we don't need to squeeze every concept into one class... we are going to run out of space if we create another class... remidner: this belongs to Spring Security (remember when we talked about "Access Desicion Voters"?).
0b7d73d
to
654d70d
Compare
831d1e7
to
e6c3504
Compare
2514be6
to
dd88e8c
Compare
24959a7
to
6d617fe
Compare
df0a5a0
to
004e718
Compare
Please rebase into 1 commit. |
<column name="result_status_code" type="INTEGER" remarks="Result status code"/> | ||
<column name="created_date" type="TIMESTAMP WITH TIME ZONE" remarks="Creation date"/> | ||
<column name="processed_at" type="TIMESTAMP WITH TIME ZONE" remarks="Processing timestamp"/> | ||
<column name="request_body" type="TEXT" remarks="Request body"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First: keep attributes simple... rename to body
(we know that it's from a request... and there will never be another body). Alternative name payload
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also: type TEXT
is not really the way to go... it should be in the database jsonb
and Java side com.fasterxml.jackson.databind.JsonNode
. That way we can keep the structure and the parsing is done automatically.
Check https://www.baeldung.com/spring-boot-jpa-storing-postgresql-jsonb section 3.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I checked it, but couldn't resolve the mapping because the ORM needs to know how to map the attribute to a jsonb
column, and the way explaied in the tutorial uses hibernate (which doesn't work with eclipselink).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vidakovic I dont think jsonb would work with mysql or mariadb... isnt it postgres specific?
I reckon we can differentiate json and jsonb based on which engine is used if needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, jsonb
is specific to PostgreSQL, but I think it's possible to configure the column as json
for MySQL (and MariaDB) and jsonb
for PostgreSQL.
The challenge I'm facing is how to map a jsonb
attribute using EclipseLink.
</column> | ||
<column name="command_processing_status" type="VARCHAR(255)" remarks="Command processing status"/> | ||
<column name="command_result" type="TEXT" remarks="Command result"/> | ||
<column name="result_status_code" type="INTEGER" remarks="Result status code"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's name this just status
. Simpler and says everything.
<constraints nullable="false" unique="true"/> | ||
</column> | ||
<column name="command_processing_status" type="VARCHAR(255)" remarks="Command processing status"/> | ||
<column name="command_result" type="TEXT" remarks="Command result"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's name this just result
. Simpler and says everything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also: type TEXT
is not really the way to go... it should be in the database jsonb
and Java side com.fasterxml.jackson.databind.JsonNode
. That way we can keep the structure and the parsing is done automatically.
Check https://www.baeldung.com/spring-boot-jpa-storing-postgresql-jsonb section 3.
<column name="request_idempotency_key" type="VARCHAR(50)" remarks="Unique request idempotency key"> | ||
<constraints nullable="false" unique="true"/> | ||
</column> | ||
<column name="command_processing_status" type="VARCHAR(255)" remarks="Command processing status"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need yet another status... and in a different type? Let's do either integer or string or enum status...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because they should represent different things.
The integer => Represents the http status code.(200, 201, 403, 404 ...etc)
The enum => Represents the command execution status within the system. (PROCESSED, UNDER_PROCESSING, ERROR ...etc)
} | ||
|
||
@Named("mapRequestBody") | ||
default JsonNode mapRequestBody(Object body) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be done by a Spring Converter. See my previous comments above and the Baeldung link. If implemented correctly then this default function is not necessary (Spring Data JPA will automatically take care of it).
@EnableConfigurationProperties({ NoteProperties.class }) | ||
@ComponentScan("org.apache.fineract.v3.note") | ||
@ConditionalOnProperty(value = "fineract.note.enabled", havingValue = "true") | ||
public class NoteV3AutoConfiguration {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just NoteAutoConfiguration
will do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried it but there's a conflict between it and the other configuration in the upstream code.
Line 38 in 24c83b5
public class NoteAutoConfiguration { |
Here's the error logs:
fineract-fineract-1 | org.springframework.beans.factory.BeanDefinitionStoreException: Failed to process import candidates for configuration class [org.apache.fineract.ServerApplication$Configuration]: Annotation-specified bean name 'noteAutoConfiguration' for bean class [org.apache.fineract.portfolio.note.starter.NoteAutoConfiguration] conflicts with existing, non-compatible bean definition of same name and class [org.apache.fineract.v3.note.starter.NoteAutoConfiguration]
…ajor improvements with the request processing flow. This commit squashes 2 commits (Read & Write) type-safe request **Read Requests** FINERACT-2021: refactor the note GET end-points to eliminate manual serialization with GSON. - Implemented a new custom module for the note API. - Used Spring Web-MVC annotations instead of JAX-RS. - Dispensed the redundant query parameters like "fields, prettyPrint ..etc" - Implemented a new `CommonWebConfiguration` for establishing the security configurations (not complete yet). - Implemented configuration modules (core, starter, configuration) for configuring the note module. - Implemented a new `docker-compose-api-v3.yml` configuration file. **Write Requests** FINERACT-2110: Add basic structure for the new mirror type-safe API (Commands Proof of Concept) - Made a unique command (Request & Response) for each http method. - Define a new way for locating the dedicated handlers. - Add basic structure for the new mirror type-safe API (Commands). - Add a new domain module for storing `Command` entity and add a repository for it. - Re-Implement the logic within the Note Write Service. - Implement a `ControllerAdvice` class for handling the exceptions that's thrown when working with note API.
004e718
to
f59cbbb
Compare
This pull request seems to be stale. Are you still planning to work on it? We will automatically close it in 30 days. |
Description
Describe the changes made and why they were made.
Ignore if these details are present on the associated Apache Fineract JIRA ticket.
Checklist
Please make sure these boxes are checked before submitting your pull request - thanks!
Write the commit message as per https://github.com/apache/fineract/#pull-requests
Acknowledge that we will not review PRs that are not passing the build ("green") - it is your responsibility to get a proposed PR to pass the build, not primarily the project's maintainers.
Create/update unit or integration tests for verifying the changes made.
Follow coding conventions at https://cwiki.apache.org/confluence/display/FINERACT/Coding+Conventions.
Add required Swagger annotation and update API documentation at fineract-provider/src/main/resources/static/legacy-docs/apiLive.htm with details of any API changes
Submission is not a "code dump". (Large changes can be made "in repository" via a branch. Ask on the developer mailing list for guidance, if required.)
FYI our guidelines for code reviews are at https://cwiki.apache.org/confluence/display/FINERACT/Code+Review+Guide.