Conversation
|
Design Choices
Question for reviewer I think adding the breeding-insight reference source is a good idea. Wonder if a URL to the breeding insight system would better to use as the reference source? |
| Provider<BrAPIClientProvider> brAPIClientProvider; | ||
|
|
||
| public TraitsAPI getTraitsAPI(BrAPIClientType clientType){ | ||
| BrAPIClient brAPIClient = brAPIClientProvider.get().getClient(clientType); |
There was a problem hiding this comment.
Does BrAPIClientType need to be passed in here? Wouldn't the various getXAPI methods know what client type to use?
There was a problem hiding this comment.
I think there will be cases when you want to use the same API for multiple brapi services. One example that comes to mind is that when we create a study, we are going to need to create that study in the phenotypic service to attach our phenotypic data to it, and in the genotypic service to attach our genotyping data to it. The client type acts a solution to that so the dao can target the services it needs to.
| throw new InternalServerException(e.getMessage()); | ||
| } | ||
|
|
||
| Map<String, BrApiVariable> brApiVariableMap = new HashMap<>(); |
There was a problem hiding this comment.
If you end up adding the reference source you could use that as a query parameter in the client call. I guess this checking for a reference id would remain but at least you'd only be iterating over breeding insight variables.
There was a problem hiding this comment.
I think the call to the BrAPI backend should make use of /search/variables. You send a ObservationVariableSearchRequest which includes a list of externalReferenceIds and a list of externalReferenceSources.
There was a problem hiding this comment.
Yep, agreed! I forgot that those search endpoints existed. Looks like our client library doesn't implement the search endpoints yet though. @nickpalladino is that true? Maybe do it this way for now and create a card for changing it in the future?
There was a problem hiding this comment.
That's right, client doesn't currently implement the /search/variables endpoints. I agree with a future improvement to do that.
| String methodClass; | ||
| String description; | ||
| String formula; | ||
|
|
There was a problem hiding this comment.
Guessing we don't care about additionalInfo, bibliographicalReference, externalReferences, ontologyReference?
There was a problem hiding this comment.
Yeah that is what I'm thinking. They aren't of much use to our domain model.
timparsons
left a comment
There was a problem hiding this comment.
Sorry for so many comments.
I looked through everything in detail, except BrAPIServerFilterIntegrationTest.java. I want to give that some thought on ways to make it less brittle
| public class TraitController { | ||
|
|
||
| @Inject | ||
| TraitService traitService; |
There was a problem hiding this comment.
Changed and pushed.
| import java.util.UUID; | ||
|
|
||
| @Filter("/**") | ||
| public class BrAPIServerFilter extends OncePerRequestHttpServerFilter { |
There was a problem hiding this comment.
Chris and I talked about this earlier. We agreed that this filter should be updated as follows:
- Restrict the
@Filterpattern to focus on/programs/{programId} - Remove
@BrAPIServiceannotation - Update tests to include tests for using the default BrAPI service and a program specified BrAPI service
Additional change request: Update the name of this filter to be BrAPIServiceFilter
| String defaultBrAPIUrl; | ||
| @Inject | ||
| ProgramService programService; | ||
| @Inject | ||
| BrAPIClientProvider brAPIClientProvider; |
There was a problem hiding this comment.
Changed and pushed.
| Map<String, Object> params = routeMatch.getVariableValues(); | ||
| if (params.get("programId") != null) { | ||
| UUID programId = UUID.fromString(params.get("programId").toString()); | ||
| ProgramBrAPIEndpoints programBrAPIEndpoints = programService.getBrapiEndpoints(programId); |
There was a problem hiding this comment.
Given that this is a filter, it will execute before the program-specific endpoint is executed. I think there should be a check that the programsBrAPIEndpoints object isn't null in the case that an invalid program id is passed in.
There was a problem hiding this comment.
I'm not sure I am following on this one. If the filter is called on an endpoint, there will always be a programId (with the new filter changes). In the programService we can check if the programId that was sent in exists. If the UUID is invalid, there will be an internal server exception thrown on line 57. I could see us catching that exception and throwing a BadRequestException now that I think of it though. Did that cover your comment, or were you talking about something else?
There was a problem hiding this comment.
I think it should throw a 404, not a 500, as the endpoint that's being requested would also throw a 404 if the filter wasn't there (because the programID doesn't exist).
There was a problem hiding this comment.
Yep, that makes sense! I'll catch that and throw a 404.
|
|
||
| private DSLContext dsl; | ||
| @Inject | ||
| BrAPIProvider brAPIProvider; |
There was a problem hiding this comment.
Changed and pushed.
|
|
||
| @Test | ||
| @SneakyThrows | ||
| public void getTraitsExistsInBrAPINotInSystem() { |
There was a problem hiding this comment.
With this test, are you expecting the returned list of traits for the program to be only the two that exist in the system (meaning the third is not included in the response to the client)? If so, I think you should add an assertion that the list returned to the client is == 2
There was a problem hiding this comment.
Changed and pushed.
| assertEquals(HttpStatus.OK, response.getStatus()); | ||
|
|
||
| JsonObject result = JsonParser.parseString(response.body()).getAsJsonObject().getAsJsonObject("result"); | ||
| checkTraitFullResponse(result, validTraits.get(0), brApiVariable1); |
There was a problem hiding this comment.
If the trait exists in BrAPI, but not in the system, shouldn't the response from the system be a 404?
There was a problem hiding this comment.
Yeah it should be. Looks like this test needs to be fixed.
There was a problem hiding this comment.
I removed this test since it was really a duplicate of getTraitSingleNotExist. Thanks for the catch!
| public void getTraitSingleBrAPIError() { | ||
|
|
||
| reset(variablesAPI); | ||
| when(variablesAPI.getVariables()).thenThrow(new HttpNotFoundException("test")); |
There was a problem hiding this comment.
I think you should have the mocked error be an internal server error, as you've already got a test for the trait existing in the system, but not in BrAPI (aka Not Found).
There was a problem hiding this comment.
Changed and pushed.
|
|
||
| List<String> jsonSynonyms = new ArrayList<>(); | ||
| traitJson.get("synonyms").getAsJsonArray().iterator().forEachRemaining(element -> jsonSynonyms.add(element.getAsString())); | ||
| assertLinesMatch(jsonSynonyms, brApiVariable.getTrait().getSynonyms(), "Synonyms don't match"); |
There was a problem hiding this comment.
Do you know if order is important for this assertion? If so, it may be worth exploring an alternative way to compare if the two lists have the same values (order independent).
There was a problem hiding this comment.
Order does matter I believe, but I was trying to be lazy :P I can implement unordered list checking on these.
There was a problem hiding this comment.
I think Apache commons may have a utility to do this, so you don't have to write the code
There was a problem hiding this comment.
I just sorted the lists before checking them. Changed and pushed.
| ProgramEntity validProgram; | ||
| @Value("${micronaut.brapi.server.url}") | ||
| String defaultBrAPIUrl; | ||
|
|
||
| ListAppender<ILoggingEvent> loggingEventListAppender; | ||
| @Inject | ||
| ProgramService programService; | ||
| @MockBean(ProgramService.class) | ||
| ProgramService programService() { return mock(ProgramService.class);} | ||
| @Inject | ||
| BrAPIClientProvider brAPIClientProvider; | ||
| @MockBean(BrAPIClientProvider.class) | ||
| BrAPIClientProvider brAPIClientProvider() { return mock(BrAPIClientProvider.class, CALLS_REAL_METHODS); } | ||
|
|
||
| @Inject | ||
| DSLContext dsl; | ||
| @Inject | ||
| ProgramDao programDao; | ||
|
|
||
| @Inject | ||
| @Client("/${micronaut.bi.api.version}") | ||
| RxHttpClient client; |
There was a problem hiding this comment.
Agreed. I'm not going to switch over our other tests, both those should probably have private properties too (all public right now).
There was a problem hiding this comment.
Changed and pushed.
Yep I can default to that! |
I think I found a better way to test the BrAPIServerFilter. I will push it up with the other changes. Edit: New way is slightly better in that it doesn't rely on the side effects of error logging in the brapi client. I was able to spy on the brapi client to check the url before it executed and do an assertion there. The only downside is that the assertion is thrown in the micronaut code not in our testing code, so we aren't able to capture it easily. When I call the endpoints I expect them to fail because there will be nothing returned from the brapi server (I think this is fixable. Just a lot of time investment). Because of this, on a successful test, the api will still return an internal server error. In the end, I'm still checking the logs for my expected error vs. assertion error. These tests could still use improvement. But, they is cleaner and more solid than they were before. |
| .build(); | ||
| } | ||
|
|
||
| public void checkBrAPIClientExecution(String expectedUrl) throws AssertionFailedError { |
There was a problem hiding this comment.
Just had a thought on this (and if you'd rather create another story for this, I understand). What about using a "Promise"-like approach (in Java they're called "Future"s). Essentially something like this:
In this method, you'd create a Future, typed to return a Boolean. In the Answer object you've created, have it complete the Future instead of using the JUnit assertEquals. Lastly, have this method return the created future. In the test method, you'd have a variable for the future, and after you initiate the API call, you'd have an assertEquals method that would have an expected value of true, and an actual value of future.get(). Here's an example:
public Future<Boolean> checkBrAPIClientExecution(String expectedUrl) throws AssertionFailedError {
CompletableFuture<Boolean> future = new CompletableFuture<>();
// Takes advantage of brapi library code to mimic no return results from api.
Answer<Optional<Object>> checkBrAPIExecution = new Answer<Optional<Object>>() {
@Override
public Optional<Object> answer(InvocationOnMock invocation) throws AssertionFailedError {
BrAPIClient executingBrAPIClient = (BrAPIClient) invocation.getMock();
// Check that our url is correct
future.complete(expectedUrl.equals(executingBrAPIClient.brapiURI()));
return Optional.empty();
}
};
...
return future;
}and in your test method:
Future<Boolean> brapiUrlCheck = checkBrAPIClientExecution(phenoUrl);
Flowable<HttpResponse<String>> call = client.exchange(
GET("/programs/" + validProgram.getId() + "/traits?full=true")
.contentType(MediaType.APPLICATION_JSON)
.cookie(new NettyCookie("phylo-token", "test-registered-user")), String.class
);
...
assertEquals(true, brapiUrlCheck.get(), "Expected url was not used");I think if you went this way, you wouldn't need to look at the logs at all.
There was a problem hiding this comment.
Great idea! I implemented this and it worked as expected. The changes are pushed. Thanks for the idea!
|
|
||
| public void checkTraitResponse(JsonObject traitJson, Trait trait) { | ||
|
|
||
| assertEquals(traitJson.get("id").getAsString(), trait.getId().toString(), "Ids don't match"); |
There was a problem hiding this comment.
It looks like you pulled a me and swapped the expected and actual parameters, not that it's a big deal.
There was a problem hiding this comment.
Changed and pushed
Changed and pushed |
timparsons
left a comment
There was a problem hiding this comment.
Two small comments, but everything else looks great!!
| @Value("${micronaut.brapi.server.url}") | ||
| private String defaultBrAPIUrl; |
There was a problem hiding this comment.
Sorry I didn't catch this sooner, we will probably want to have a default BrAPI URL for each of the services (core, pheno, geno)
There was a problem hiding this comment.
changed and pushed
src/main/resources/application.yml
Outdated
| brapi: | ||
| server: | ||
| url: ${brapi.server.url:`https://test-server.brapi.org/`} | ||
| reference-source: ${brapi.server.reference-source:`breeding-insight.org`} |
There was a problem hiding this comment.
Sorry I didn't catch this sooner. It may be cleaner to have properties that we've defined NOT be under the micronaut namespace (see web at the bottom of application.yml as an example).
| @@ -0,0 +1,10 @@ | |||
| package org.breedinginsight.api.model.v1.request.query; | |||
There was a problem hiding this comment.
Looks like all the newly added files are missing the license header. Also, you can set up intellij so that it will add them automatically when you create a new file.
There was a problem hiding this comment.
changed and pushed
Includes ONT-48, ONT-47, and INF-91.
This feature sets up infrastructure for interfacing with third party brapi servers. The infrastructure is set up in such a way that allows for different servers for each program in the future. This also implements the trait endpoints for reading lists of traits and individual traits for our system. It also sets up a new way of setting data for tests using fannypack.
Design Choices
@BrAPIService. This flags a request filter to get the programId from the request url and retrieve the target url for it.Boolean fullthat indicates where the information for traits that we have in our system should be returned, or the combined information for our database and our brapi servers should be returned. This should speed up our displaying of lists and make the UI more responsive.Internal Server Errors. If we find the trait in our system, but it doesn't exist in the brapi server, that is going to be due to a server error because something went wrong in our storage or retrieval. Returning a 404 would be misleading because it was an error in the code somewhere. Looking for thoughts on this one since this is a different behavior than what is shown in the sequence diagrams.Questions for reviewer
I was thinking to set an external reference source configuration in the application.yml now. If we set a reference source like,
breeding-insightthen we wouldn't need to worry about accidentally retrieving data that comes from other sources. This allows more specific retrieval of external references. This might also help in retrieving and debugging data issues in our services later on. What are your thoughts on adding the reference source vs. using only UUID?Review