Skip to content

Conversation

@aochsner
Copy link
Contributor

Left todo:

  • Fix client (shape of json changed a bit...)
  • Let JPA manage schema (get rid of schema.sql)
  • Push more filtering in queries (going to be harder around interval overlap)
  • Ensure only "expand" one to many relationships when asked? (auto expand many to one for now?)

BKIT-140
BKIT-140
case sensitivity when hibernate validates schema
BKIT-140
Bit of WIP until booking works
BKIT-140
still needed for validations
BKIT-140
@aochsner aochsner requested a review from mathomas December 19, 2017 18:46
@ManyToOne(optional = false)
val user: User,
@Id @GeneratedValue(generator = "uuid2") @GenericGenerator(name = "uuid2", strategy = "uuid2")
val id: String? = null
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally moved optional IDs to end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I slightly prefer a degree of isomorphism between the schema and the code, and like PKs first in a schema, but I can't what JPA will do in the schema anyway with regard to order (maybe it sorts keys to the top?).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair point... i'll have a looksie as to what it actually does

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hibernate: drop table bookable if exists
Hibernate: drop table booking if exists
Hibernate: drop table location if exists
Hibernate: drop table user if exists
Hibernate: create table bookable (id varchar(255) not null, closed boolean not null, reason varchar(255) not null, name varchar(255) not null, location_id varchar(255) not null, primary key (id))
Hibernate: create table booking (id varchar(255) not null, end timestamp not null, start timestamp not null, subject varchar(255) not null, bookable_id varchar(255) not null, user_id varchar(255) not null, primary key (id))
Hibernate: create table location (id varchar(255) not null, name varchar(255) not null, time_zone varchar(255) not null, primary key (id))
Hibernate: create table user (id varchar(255) not null, external_id varchar(255) not null, family_name varchar(255) not null, given_name varchar(255) not null, primary key (id))
Hibernate: alter table bookable add constraint UK_6ietqnvn35wqiovo8qrmwk2ne unique (location_id, name)
Hibernate: alter table location add constraint UK_sahixf1v7f7xns19cbg12d946 unique (name)
Hibernate: alter table user add constraint UK_4eu2tvn9rj53a93fufx7ayr20 unique (external_id)
Hibernate: alter table bookable add constraint FK1dc19oypan7r3ry418vn2h5sc foreign key (location_id) references location
Hibernate: alter table booking add constraint FKtbhx4q4q4m1kv4muhynct3p81 foreign key (bookable_id) references bookable
Hibernate: alter table booking add constraint FKkgseyy7t56x7lkjgu3wah5s3t foreign key (user_id) references user

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so looks like it does the right thing i'd like to see from a schema perspective wrt order

fun getBookable(@PathVariable("locationId") location: String, @PathVariable("bookableId") bookable: String): BookableResource {
locationRepository.getLocations().find { it.id == location } ?: throw LocationNotFound()
return BookableResource(bookableRepository.getAllBookables().find { it.id == bookable } ?: throw BookableNotFound())
fun getBookable(@PathVariable("locationId") location: Location?, @PathVariable("bookableId") bookable: Bookable?): BookableResource {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spring will automatically use the locationRepo to map locationId -> location object so we don't need our own dependency to it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Magic is cool. And scary and confusing. Oh well, can't have everything :-)
And this, I think, answers my question above, re "witchcraft" (?).

@Id @GeneratedValue(generator = "uuid2") @GenericGenerator(name = "uuid2", strategy = "uuid2")
val id: String? = null
) {
val name get() = "$givenName $familyName"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sort of a hack to reuse the User entity as the User response but it still seems "simple enough"...

# generate-ddl: false
show-sql: true
hibernate:
ddl-auto: validate
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these settings might change once/if we let JPA manage the schema... for now still driven by schema.sql

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of fear us getting into a "deadlocked" deployment again if we fail to sync schema.sql and annotations and thus startup fails. You?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree... hoping the existence of schema.sql is short term... next go around will eliminate it

USER_ID VARCHAR(36) NOT NULL,
PRIMARY KEY (USER_ID),
EXTERNAL_USER_ID VARCHAR(128) NOT NULL,
CREATE TABLE user (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

msyql & hibernate schema validation seems to be case sensitive, although nothing else seems to be...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird. I actually like lower-case table names in case of MySql case-sensitive installation (hate having to type capital letter table names in queries).

PRIMARY KEY (USER_ID),
EXTERNAL_USER_ID VARCHAR(128) NOT NULL,
CREATE TABLE user (
ID VARCHAR(36) NOT NULL,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

took the naming conventions from JPA/Hibernate instead of adding to JPA annotations

"bookableId": "aab6d676-d3cb-4b9b-b285-6e63058aeda8",
"bookable": {
"id": "aab6d676-d3cb-4b9b-b285-6e63058aeda8"
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@frostiebot @lizziesz one example of how the shape of responses change...generally if you had an id coming back now you have a whole object (we only validate the "id" but everything comes back...)...

we can have further discussions as to whether this is OK or whether we shouldn't do that... or whether we should replace ids w/ URIs etc etc

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows for flexibility in future and is better now before things harden more (IMO).

"id": "aab6d676-d3cb-4b9b-b285-6e63058aeda8",
"locationId": "b1177996-75e2-41da-a3e9-fcdd75d1ab31",
"location": {
"id": "b1177996-75e2-41da-a3e9-fcdd75d1ab31"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

registry.addConverter(String::class.java, Booking::class.java) { id ->
when (id) {
BookingControllerMockMvcTests.booking.id -> BookingControllerMockMvcTests.booking
else -> null
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this magic is needed to handle the id -> object "lookup" that spring does for us now... almost decided this was too "magic" and went back to what we had... thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Witchcraft. Burn it.

Just kidding! Too disruptive to change back away from it based on the work you've done here, IMO. But my brain still recoils to getFoo(Foo).

Copy link
Contributor

@mathomas mathomas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much elegant. So better.
Thanks for your work on this!
My comments are mostly observations. See what you think, and merge away. Fire in the hole!

return allBookings
.filter { booking ->
val timezone = bookableTimezones[booking.bookableId] ?: throw IllegalStateException("Encountered an incomplete booking: $booking. Not able to determine booking's timezone.")
val timezone = booking.bookable.location.timeZone
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed due to constraints guaranteeing presence?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

@GetMapping("/{id}")
fun getBooking(@PathVariable("id") bookingId: String, @AuthenticationPrincipal user: UserPrincipal): Booking =
bookingRepository.getAllBookings().find { it.id == bookingId }.let { maskSubjectIfOtherUser(it ?: throw BookingNotFound(), user) }
fun getBooking(@PathVariable("id") booking: Booking?, @AuthenticationPrincipal user: UserPrincipal): Booking =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What witchcraft is this? Am looking at the diffs in the browser, so probably just need context.

@ManyToOne(optional = false)
val user: User,
@Id @GeneratedValue(generator = "uuid2") @GenericGenerator(name = "uuid2", strategy = "uuid2")
val id: String? = null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I slightly prefer a degree of isomorphism between the schema and the code, and like PKs first in a schema, but I can't what JPA will do in the schema anyway with regard to order (maybe it sorts keys to the top?).

fun getBookable(@PathVariable("locationId") location: String, @PathVariable("bookableId") bookable: String): BookableResource {
locationRepository.getLocations().find { it.id == location } ?: throw LocationNotFound()
return BookableResource(bookableRepository.getAllBookables().find { it.id == bookable } ?: throw BookableNotFound())
fun getBookable(@PathVariable("locationId") location: Location?, @PathVariable("bookableId") bookable: Bookable?): BookableResource {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Magic is cool. And scary and confusing. Oh well, can't have everything :-)
And this, I think, answers my question above, re "witchcraft" (?).

"bookings" in expand ?: emptyList() ->
allBookings
.filter { booking -> booking.bookableId == bookable.id }
bookingRepository.findByBookable(bookable)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we have an O(n^2) (or some variety of such) thing here, I think, WRT database calls. With the data we have today, no big deal, but someday ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yess..... thanks for point that out... forgot to clean this up... will leave in for this pr and fix right after

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

although, maaaaaybe some hibernate caching (all in the same transaction) alleviates us from being really dumb about it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't complain. Really nice work here. I'm loving the reduction in code. Spring Data is pretty darn nice.

# generate-ddl: false
show-sql: true
hibernate:
ddl-auto: validate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of fear us getting into a "deadlocked" deployment again if we fail to sync schema.sql and annotations and thus startup fails. You?

registry.addConverter(String::class.java, Booking::class.java) { id ->
when (id) {
BookingControllerMockMvcTests.booking.id -> BookingControllerMockMvcTests.booking
else -> null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Witchcraft. Burn it.

Just kidding! Too disruptive to change back away from it based on the work you've done here, IMO. But my brain still recoils to getFoo(Foo).

}

@TestConfiguration
class InternalConfig {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any possibility of DRY?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mean between tests? although they'd have to share test data assumptions...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Location's mapping is defined in multiple tests. Just wonder if the @TestConfiguration can be DRY for that Entity, for example. It's not a major thing -- the code is write once (in a few locations), and never revisit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a stupid question, too. I'm really not familiar with Spring Data beyond the benefits, WRT repos, etc. The magical parts and implementation niceties are still a mystery.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it's for the test...although there mgiht be some ways to factor stuff out

@aochsner aochsner merged commit e81d62a into master Dec 21, 2017
@aochsner aochsner deleted the bkit-140-jpa branch December 21, 2017 21:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants