-
Notifications
You must be signed in to change notification settings - Fork 23
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
adapter: DataImports API and refactoring #322
Conversation
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Datasource needs to exist before updating", e); | ||
} | ||
public void updateDatasource(@PathVariable Long id, @Valid @RequestBody Datasource updateConfig) { | ||
this.handleErrors(() -> { datasourceManager.updateDatasource(id, updateConfig); return null;}); |
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.
the return null;
is necessary as the handleErrors
function expects a CheckedSupplier
which returns something.
It would be an option to overload that function expecting a (to be defined) CheckedRunnable
that returns void, but that would mean more code duplication. It could have been so pretty 😢 ...
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.
Nice work introducing application specific exceptions. I really like that, it is much cleaner now!
Instead of using our own handleErrors(...)
function, you could also have used these fancy Spring annotations: @ExceptionHandler
and @ControllerAdvice
. Here and here you can find some tutorials how to use these annotations if you want to go with the spring way of handling exceptions. This is just for reference, for me, it is also ok sticking with the handleErrors(...)
function.
public class Mappings { | ||
public static final String IMPORT_PATH = "/preview"; | ||
public static final String RAW_IMPORT_PATH = "/preview/raw"; | ||
public static final String FORMAT_PATH = "/formats"; | ||
public static final String PROTOCOL_PATH = "/protocols"; | ||
public static final String VERSION_PATH = "/version"; | ||
|
||
public static final Map<Class, HttpStatus> ERROR_MAPPING = Map.of( |
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 wonder if a status code annotation directly at the exception is better?
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
class CustomException extends RuntimeException {}
Probably you had some thoughts on that, do you mind sharing them? :)
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 wonder if a status code annotation directly at the exception is better?
Probably you had some thoughts on that, do you mind sharing them? :)
I honestly had no clue about the way you are "supposed" to do it in Spring. Gonna have to take a look at it in detail. The solution I went for was the first one that came to mind to avoid repetition in every endpoint method.
|
||
import lombok.NoArgsConstructor; | ||
|
||
@NoArgsConstructor |
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 would have assumed that lombok has some help for exceptions, but seems like the PR is not yet merged: projectlombok/lombok#2702
Datasource existing = datasourceRepository.findById(id) | ||
.orElseThrow(() -> new IllegalArgumentException("Datasource with id " + id + " not found")); | ||
public void updateDatasource(Long id, Datasource update) throws DatasourceNotFoundException { | ||
Datasource existing = datasourceRepository.findById(id).orElseThrow(() -> new DatasourceNotFoundException(id)); |
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 think these chains are hard to read.. I guess we should introduce some linter or so in the future xD
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 actually kinda like chains like that, guess that comes from my preference for functional programming 😁.
@@ -20,52 +22,48 @@ | |||
public class DatasourceEndpoint { | |||
private final DatasourceManager datasourceManager; | |||
|
|||
private <T> T handleErrors(CheckedSupplier<T> function) throws ResponseStatusException { |
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.
duplicate with other endpoints => should we use a convert method and place it next to the mapping?
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.
There are two different Mappings classes though, one for the Adapter, and one for the Datasources.
I have not found a good way to avoid this duplication yet. Someone got any ideas?
|
||
private Date timestamp; | ||
|
||
@ManyToOne(fetch = FetchType.EAGER) |
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.
That means the datasource is always also retrieved, right? In general, not a bad idea, but why do we not serialize it then? Just a little confused, do we need it?
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 its id
to create the URI to the data in DataImportMetadata
.
Also the annotations are necessary for Spring to create the database tables and fields properly I think.
@OneToMany(cascade = CascadeType.ALL, mappedBy = "datasource") | ||
@JsonIgnore | ||
@EqualsAndHashCode.Exclude // needed to avoid an endless loop because of a circular reference | ||
private Set<DataImport> dataImports; |
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.
When are they fetched? Always on a get? Might lead to a huge overload once we had run 100 imports for a source... Maybe we should not link these concepts at all and just provide a datasourceId in the DataImport?
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 put in a fetch = FetchType.LAZY
which should resolve your worries about performance.
Maybe we should not link these concepts at all and just provide a datasourceId in the DataImport?
Usually I prefer things doing manually as well, I am not sure if all the Spring SQL magic still works then though. And to be honest, the foreign keys generated by using those annotations are kinda neat.
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.
If we do not want this field, we can safely remove it, because the datasource
field in the DataImport
class is still annotated with @ManyToOne
. This should be sufficient for Hibernate the generate the foreign keys.
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 use that field to generate the result of the /datasources/{id}/imports
call though. But of course that could also be done using a function in the repository.
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.
Yeah, I would definitely go with a separate findAllByDatasourceId
function in the DataImportRespository. This will be much faster because querying all other properties of the Datasource is useless when just return the list of DataImports.
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.
Problem I see with this approach:
We want to differentiate between "return empty array because no data imports were found for the data source" and "return 404 because datasource does not exist".
If we use a function called findAllByDatasourceId
it would just return nothing if the datasource does not exist at all.
So we kinda need to fetch the datasource details anyways (or check if it exists, which should not be a big difference).
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.
Ah, I haven't considered that 🙈
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.
Good point.. I am kind of unsure what is best...
I guess lazy loading is misleading when reading code - or at least I'd assume the relation is fetched since I don't work with Spring too often. So personally, I'd not have the reference and make a separate call to prevent these uncertainties from happening. The readability would be clear:
- if datasource does not exist => return 404
- if imports do not exist => return []
The obvious disadvantage: two database requests instead of one..
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.
The obvious disadvantage: two database requests instead of one..
Well, I think having @OneToMany
with FetchType.LAZY
will also be two database requests...
I updated the PR and changed the exception handling to be more spring-y (?, springful? springesque?). @sonallux could you please take a look? Also that was a very short endeavor into the functional side of Java... 😢 |
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 two very minor things, otherwise LGTM. The exception handling is now much more springish 👍
This PR also fixes #232 🚀
...r/src/main/java/org/jvalue/ods/adapterservice/datasource/api/rest/v1/DataImportEndpoint.java
Outdated
Show resolved
Hide resolved
...r/src/main/java/org/jvalue/ods/adapterservice/datasource/api/rest/v1/DataImportEndpoint.java
Outdated
Show resolved
Hide resolved
Nice! I think this feature is a good step forward :) |
closes #135
I have a very bad habit of not commiting while I work, so sorry in advance for this mess of a PR consisting of 1 huge commit.
This PR contains the following:
/trigger
on a datasource. DataImports are saved in the database and can be accessed using new API endpoints (see README and related issue).Adapter
,DatasourceManager
, ...) throw only application-specific exceptions now. Those exceptions are translated intoRestResponseExceptions
with suitable status code in the endpoint classes. I got a bit carried away and thus this is done using aMap
of exceptions to status codes and a fancy method that takes a custom lambda. Check the code review for a caveat.