-
Notifications
You must be signed in to change notification settings - Fork 14
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
Feature/1693 api v3 versioned model - Dataset v3 implementation #2046
Conversation
…e contours, empty DatasetControllerV3 to test
- login allowed at /api/login & /api-v3/login - v2/v3 BaseRestApiTest distinquished
…der and no content -> IT updated
…version, includes a sample CR)
…st and post-import + IT to prove correct behavior
…mport + IT to prove correct behavior - fix with common location processing with segment stripping (+normalization)
… GET datasets/{name}/{version}
…/audit-trail added
…ion} now works for # or 'latest' (IT = regression test)
…{name}/{version} and /{name}/latest - improved
…- supports latest for as version-expression, impl for datasets improved by actual existence checking + IT test cases for non-existing/non-latest queries
…properties - supports latest for as version-expression; get impl is unvalidated, put impl checks validity - login is now common, under /api/login for both v2 and v3 (did not work previously)
…properties - supports latest for as version-expression; get impl is unvalidated, put impl checks validity - extended for different validation cases - login is now common, under /api/login for both v2 and v3 (did not work previously)
…ture -> CompletableFuture (mistake reverted)
… added. IT mostly adjusted, but there are todos - DatasetServiceV3 introduced to carry difference in behavior to DatasetService. original entity validation has been divided into create-validation and regular-entity validation. - buildfix for VersionedModelServiceTest
…sion}` in proper validations
…asets/dsName/version/rules, GET datasets/dsName/version/rules/# + 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.
I like it overall, just some small comments.
Also, do you plan to change the frontend to use this api?
data-model/src/main/scala/za/co/absa/enceladus/model/versionedModel/VersionedSummary.scala
Outdated
Show resolved
Hide resolved
rest-api/src/main/scala/za/co/absa/enceladus/rest_api/services/DatasetService.scala
Outdated
Show resolved
Hide resolved
rest-api/src/main/scala/za/co/absa/enceladus/rest_api/services/DatasetService.scala
Outdated
Show resolved
Hide resolved
rest-api/src/main/scala/za/co/absa/enceladus/rest_api/services/DatasetService.scala
Outdated
Show resolved
Hide resolved
rest-api/src/main/scala/za/co/absa/enceladus/rest_api/services/DatasetService.scala
Outdated
Show resolved
Hide resolved
@GetMapping(Array("/{name}/export")) | ||
@ResponseStatus(HttpStatus.OK) | ||
def exportLatestEntity(@PathVariable name: String): CompletableFuture[String] = { | ||
versionedModelService.exportLatestItem(name) // todo: remove in favor of the above? (that supports /{name}/latest/export) |
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.
From my point of view, this can stay
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.
Well, this is part of the v3 API outline. I consider this misleading, because since you have the option to export specific versions already ( at .../{model}/{name}/{versionEitherNumberOrLatestKeyword}/export, having the option for export here would have to have another meaning - e.g. export the whole entity with all the versions. But since this is designed as the latest export as well, I consider this unnecessary, and as I said, even misleading.
That is why I am proposing to omit this endpoint entirely.
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 that if we gave the "latest" version we can omit this.
Another point to think about can be - export/import being on the entity itself. Export having parameters of name and version.
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.
After even further discussion, I have decided to remove the endpoint based on the following logic:
The structure of API endpoints representing a collection should, in general, behave in the following fashion:
GET /entityNameInPlural
-> list items or provide information about all/more itemsGET /entityNameInPlural/individualEntityKeyEgId
-> give information about a specific entity
By that logic, GET /{name}/export
would be appropriate if all entities or information about all/more was returned -- perhaps all versions, while GET /{name}/{version}/export
would export just a single version.
But since we mapped just the single version (the latest one) export to a collection's endpoint, the logic does not fit. Moreover, given the fact that we also accept latest
as a valid version identifier, it seem much more appropriate to use GET /{name}/{version}/export
here (specifically GET /{name}/latest/export
)
In the end, I think the UI should get changed to use this API. But that is obviously outside of the scope of the API update PRs. |
- v2 ignores this added information - needs
@@ -16,3 +16,5 @@ | |||
package za.co.absa.enceladus.model.versionedModel | |||
|
|||
case class VersionedSummary(_id: String, latestVersion: Int) | |||
|
|||
case class VersionList(_id: String, versions: Seq[Int]) |
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.
Couldn't Range
be used for versions
?
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.
In theory, it could. Or even VersionedSummary
with just the latest version would do (assuming that all entities start with v1).
Attachments are the exception, they take after the schema version and there may be holes, but attachments are not exposed directly.
Then there is the matter of not being able to prove that all versions are actually there in the DB, we assume they should be, but... how sure can you be.
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.
Perhaps let's just use just VersionedSummary - with the latest version
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.
Done.
data-model/src/main/scala/za/co/absa/enceladus/model/versionedModel/VersionedSummary.scala
Show resolved
Hide resolved
rest-api/src/main/scala/za/co/absa/enceladus/rest_api/SpringFoxConfig.scala
Outdated
Show resolved
Hide resolved
@@ -113,7 +113,7 @@ class DatasetController @Autowired()(datasetService: DatasetService) | |||
def replaceProperties(@AuthenticationPrincipal principal: UserDetails, | |||
@PathVariable datasetName: String, | |||
@RequestBody newProperties: Optional[Map[String, String]]): CompletableFuture[ResponseEntity[Option[Dataset]]] = { | |||
datasetService.replaceProperties(principal.getUsername, datasetName, newProperties.toScalaOption).map { | |||
datasetService.updatePropertiesV2(principal.getUsername, datasetName, newProperties.toScalaOption).map { |
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 guess the V2 and V3 suffixes are because of Spring autowiring?
(Looks ugly to me, to be honest 😄 )
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 has nothing to do with spring autowiring. This is just a way to have V2 and V3 implementation in some services so that the v2 version can be removed easily when only v3 is to stay.
If we want to keep v2 and v3 long-term, this would require to create separate services, e.g. datasetService
and datasetServiceV3
. One of these solutions is, I believe, necessary if you want to keep the original implementation working.
It then begs the question - what is the intended lifetime of V2 API? Knowing this better, it could help to design better: copy-paste-and-change-a-few-things knowing that the V2 impl will be gone soon vs. keeping v3 extending v2 (no code duplication, but harder removal)
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 v3 method moved to DatasetServiceV3, so now it is tidier. Thanks for the tip, @benedeki.
rest-api/src/main/scala/za/co/absa/enceladus/rest_api/services/DatasetService.scala
Show resolved
Hide resolved
rest-api/src/main/scala/za/co/absa/enceladus/rest_api/services/DatasetService.scala
Show resolved
Hide resolved
rest-api/src/main/scala/za/co/absa/enceladus/rest_api/services/DatasetService.scala
Outdated
Show resolved
Hide resolved
validation <- for { | ||
generalValidation <- validate(item) | ||
creationValidation <- validateForCreation(item) | ||
} yield generalValidation.merge(creationValidation) |
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 will execute the Futures in serial manner.
validation <- for { | |
generalValidation <- validate(item) | |
creationValidation <- validateForCreation(item) | |
} yield generalValidation.merge(creationValidation) | |
val generalValidationFuture = validate(item) | |
val creationValidationFuture = validateForCreation(item) | |
validation <- for { | |
generalValidation <- generalValidationFuture | |
creationValidation <- creationValidationFuture | |
} yield generalValidation.merge(creationValidation) |
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 don't see it as a problem (faster multiple-at-once vs possibly not needed - I don't even see a clear winner here)
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.
TODO Let's comment in place on how the for-comprehension will only run the necessary checks up until the point where it fails first.
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.
Explanatory comment added, thanks for the tip.
for { | ||
originalValidation <- super.validate(item) | ||
propertiesValidation <- validateProperties(item.propertiesAsMap) | ||
schemaValidation <- validateSchemaExists(item.schemaName, item.schemaVersion) | ||
rulesValidation <- validateRules(item) |
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.
Ditto with concurrency:
for { | |
originalValidation <- super.validate(item) | |
propertiesValidation <- validateProperties(item.propertiesAsMap) | |
schemaValidation <- validateSchemaExists(item.schemaName, item.schemaVersion) | |
rulesValidation <- validateRules(item) | |
val originalValidationFuture = super.validate(item) | |
val propertiesValidationFuture = validateProperties(item.propertiesAsMap) | |
val schemaValidationFuture = validateSchemaExists(item.schemaName, item.schemaVersion) | |
val rulesValidationFuture = validateRules(item) | |
for { | |
originalValidation <- originalValidationFuture | |
propertiesValidation <- propertiesValidationFuture | |
schemaValidation <- schemaValidationFuture | |
rulesValidation <- rulesValidationFuture |
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.
same as above.
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.
Explanatory comment added, thanks for the tip.
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.
Looks good to me
or[String]( | ||
// api v2 | ||
regex("/api/dataset.*"), regex("/api/schema.*"), | ||
regex("/api/mappingTable.*"), regex("/api/properties.*"), | ||
regex("/api/monitoring.*"),regex("/api/runs.*"), | ||
regex("/api/monitoring.*"), regex("/api/runs.*"), | ||
regex("/api/user.*"), regex("/api/spark.*"), | ||
regex("/api/configuration.*") | ||
regex("/api/configuration.*"), |
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 not document v2 API as we do not want people to use it. Thoughts?
Maybe spring fox could have multiple profiles? Dev and non-dev
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 agree with not documenting v2
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.
So, I have changed the implementation to the following:
- JVM being supplied
-Dspring.profiles.active=dev
will result in full V2 + V3 API as before, - by default (no special profile setup) will only allow Swagger to show API V3 + import/export methods of API v2 (these we consider public API in V2 already).
Hope this is satisfactory.
Showcase DEV -Dspring.profiles.active=dev
Showcase Non-DEV
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.
Redone due to #2046 (comment).
Menas DEV API remains unchanged, for Menas non-DEV API now only contains V3 API endpoints (i.e. V2 import/export removed)
…vice and DatasetServiceV3 with renaming. VersionList removed in favor of VersionedSummary everywhere. - explanatory comments
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 I have tested all the endpoints at this point.
Have not reviewed all the code.
...src/main/scala/za/co/absa/enceladus/rest_api/controllers/v3/VersionedModelControllerV3.scala
Outdated
Show resolved
Hide resolved
versionedModelService.getAuditTrail(name) | ||
} | ||
|
||
@GetMapping(Array("/{name}/{version}/used-in")) |
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.
Does a global used-in make sense since Dataset does not need it? I think this should be specific for the entity.
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.
Here, I think it makes sense to keep it.
Since the disable is available for all entity types, so should used-in. The nice thing about it is that there can be a common approach to entities:
- making sure that there is anything preventing from disabling (used-in)
- dealing with linked references returned by used-in if any
- disabling the entity knowing that it should succeed (without surprises)
It is quite reassuring for the API user to be able to call used-in
even if currently for a certain type there cannot be any entity linked, it perhaps may be in the future?
All in all, I am a fan of having used-in
s everywhere (where disable is).
@GetMapping(Array("/{name}/export")) | ||
@ResponseStatus(HttpStatus.OK) | ||
def exportLatestEntity(@PathVariable name: String): CompletableFuture[String] = { | ||
versionedModelService.exportLatestItem(name) // todo: remove in favor of the above? (that supports /{name}/latest/export) |
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 that if we gave the "latest" version we can omit this.
Another point to think about can be - export/import being on the entity itself. Export having parameters of name and version.
...src/main/scala/za/co/absa/enceladus/rest_api/controllers/v3/VersionedModelControllerV3.scala
Outdated
Show resolved
Hide resolved
...src/main/scala/za/co/absa/enceladus/rest_api/controllers/v3/VersionedModelControllerV3.scala
Outdated
Show resolved
Hide resolved
...src/main/scala/za/co/absa/enceladus/rest_api/controllers/v3/VersionedModelControllerV3.scala
Show resolved
Hide resolved
data-model/src/main/scala/za/co/absa/enceladus/model/versionedModel/VersionedSummary.scala
Outdated
Show resolved
Hide resolved
…rt mapping removed -> reflected in IT; small updates
… removed, some ITs and comments added
…g `dev` or not.
val paths: Seq[Predicate[String]] = if (isDev) { | ||
v2devPaths ++ v3paths | ||
} else { | ||
v2prodPaths ++ v3paths |
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.
v2prodPaths ++ v3paths | |
v3paths |
So prod APIs should be only v3 paths as the import-export being "prod" is only an ABSA thing.
This would result in dev having all v2 and v3 paths and prod only v3.
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.
Thanks for the clarification, suggestion applied (along with removing the unnecessary code)
Kudos, SonarCloud Quality Gate passed! 0 Bugs No Coverage information |
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.
Code reviewed
Api tested
This PR currently implements Enceladus API v3 for
general versioned model v3
/{modelName}/{entityName}/{versions}/validate
existsdataset model v3; for the dataset API, a few changes have been done in the design:
.../properties
endpoint has no additional logic of validation (because of the existence of a general validation endpoint instead)later ADDED: schema and MT existence checking (those referenced in datasets (for MTs - in MCR))
schemas, mapping tables, and property definitions are to follow; probably in another PR.
Partially implements #1693
Closes #1611
Release notes suggestion
/api-v3/datasets/
Rest API V3 added. Includes checks on entities and is to be used externally. The API is now truly RESTful (endpoint naming, structure, methods,Location
header on creating responses). Also, Swagger API doc reacts to Spring dev profile (JVM arg-Dspring.profiles.active=dev
) to show legacy V2 API doc, too.