Skip to content
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

pull request to show code #1

Open
wants to merge 65 commits into
base: automaticallyGeneratedCode
Choose a base branch
from
Open

Conversation

paw787878
Copy link
Owner

No description provided.

Copy link

@philemone philemone left a comment

Choose a reason for hiding this comment

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

Dzięki za przesłanie rozwiązania,

Od tego momentu nie rób proszę żadnych zmian, będzie nam łatwiej przeglądać kod :)

Odnośnie PR'a:

  • Fajnie, że napisałeś dużo testów 👍
  • Ciekawi mnie dlaczego wszystkie commity dodałeś prosto do mastera a MR jest z mastera do automaticallyGeneratedCode zamiast na odwrót?
  • Plus za podzielenie pracy na commity, małe i dobrze nazwane

Choose a reason for hiding this comment

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

Dlaczego akurat Readme.txt zamiast Readme.md?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Traktuję ten pr jako diff po którym można pisać komentarze, ten pr nigdy nie zostanie zmergowany. Z tego powodu nie zwracałem uwagi na to jak nazywa się target oraz source branch. W prawdziwej pracy zrobił bym odwrotnie tak, by po zmergowaniu tego pr zmienił się stan mastera.

Readme.md było by pewnie lepszym wyborem, bo pozwoliło by mi korzystać z markdownu, pewnie jest to też bardziej standardowy wybór. Nie zwróciłem na to uwagi.

val name: String,

@Column(nullable = false)
val price: BigDecimal,

Choose a reason for hiding this comment

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

Fajnie, że nie użyłeś Double, wiesz dlaczego lepiej używać BigDecimal do operacji na walutach?

Copy link
Owner Author

Choose a reason for hiding this comment

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

double trzyma w pamięci liczbę w zapisie binarnym, a BigDecimal w postaci zapisu dziesiętnego. Z tego powodu BigDecimal może bez problemu i bez potrzeby zaokrągleń trzymać liczbę taką jak 12.01 zł, podczas gdy double może być jedynie w przybliżeniu równy tej liczbie. Też w obliczeniach finansowych 10 jako podstawa systemu liczbowego jest standardem stosowanym przy zaokrąglaniu i zapisywaniu liczb

Comment on lines +78 to +84
var amountToPay = 0.toBigDecimal()
for (ticketSeat in seatsAndTicketTypes) {
val ticketType = ticketSeat.ticketType
val seat = ticketSeat.seat
ReservationSeat(reservation, seat, ticketType).let { reservationSeatRepository.save(it) }
amountToPay += ticketType.price
}

Choose a reason for hiding this comment

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

Umiałbyś to zapisać funkcyjnie? (tak by nie używać var) ?

Copy link
Owner Author

@paw787878 paw787878 May 12, 2023

Choose a reason for hiding this comment

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

Można tak

    val reservationSeats = seatsAndTicketTypes
        .map { (seat, ticketType) ->
            ReservationSeat(reservation, seat, ticketType)
                .let { reservationSeatRepository.save(it) }
        }

    val amountToPay = reservationSeats.sumOf { reservationSeat ->
        reservationSeat.ticketType.price
    }

Rzeczywiście ładniej

@get:Pattern(regexp="[a-zA-ZAęóąśłżźćńĘÓĄŚŁŻŹĆŃ]*", message="Must contain only letters")
@get:Size(min = 3, max = 255)
@get:FirstLetterIsCapital
var name: String,

Choose a reason for hiding this comment

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

Dlaczego akurat zmienna mutowalna?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Nawet próbowałem zrobić by to było val zamiast var, gdy to robiłem nie było jeszcze annotacji @column nad tym name, więc pojawił się błąd bo próbowało zrobić property based access. Lektura internetu doprowadziła mnie do błednego wniosku, że to takie ograniczenie że na @embeddable classes nie da się użyć val w Kotlinie.

@get:Pattern(regexp="[a-zA-ZAęóąśłżźćńĘÓĄŚŁŻŹĆŃ-]*", message="Must contain only letters and hyphens")
@get:CapitalizedWordOrTwoCapitalizedWordsWithHyphen
@get:Size(min = 3, max = 255)
var surname: String,

Choose a reason for hiding this comment

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

Podobnie tutaj, dlaczego zdecydowałeś się na var? :)

Copy link
Owner Author

Choose a reason for hiding this comment

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

dokładnie taki sam błędny powód jak w przypadku name

Copy link

@lciolecki lciolecki left a comment

Choose a reason for hiding this comment

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

Fajny kodzik, dobrze się to czyta. Troszkę logika serwisów trąci repozytoriami ;)

@@ -0,0 +1,11 @@
{
"movieScreeningId": 1,
"name": "Pawel",

Choose a reason for hiding this comment

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

A co z polskimi znakami ?:)

Copy link
Owner Author

Choose a reason for hiding this comment

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

Polskie znaki są obsługiwane, niestety nie wpisałem polskich znaków do tego "scenario" skryptu. Polskie znaki znajdują się w testach

val lengthInMinutes: Int,

@Id @GeneratedValue
val id: Long = 0,

Choose a reason for hiding this comment

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

Czy ten default ma sens?

Copy link
Owner Author

Choose a reason for hiding this comment

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

hmm, alternatywą by było
val id: Long? = null
To by spowodowało, że id stał by się nullowalny, nie jestem pewien czy to dobrze, z jednej strony zmusza mnie do dodania handlowania nulli, z drugiej strony może wykryję dzięki temu jakiś błąd.
Widziałem argument, by w Javie używać boxed obiektów jako primary key, ponieważ wówczas wiadomo że jak wartość id to null to to na pewno ten null to nie jest prawdziwa wartość z bazy danych, bo w bazie danych id jest nie null.
Znalazłem taki stack overflow
https://stackoverflow.com/questions/49747891/kotlin-jpa-entity-id
ktoś tam argumentuje za używaniem takiej formy jak ja użyłem (albo przynajmniej argumentuje, że też tak można)

Jestem ciekaw jakie jest Twoje zdanie na ten temat

@Id @GeneratedValue
val id: Long = 0,
@Version
val version: Int = 0,

Choose a reason for hiding this comment

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

Czym jest version?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Jest to potrzebne by zrobić optimistic lock tutaj
com/example/ticketbookingapp/service/MovieReservationService.kt:44
Ten lock używam by zapewnić, że nawet gdy kilka użytkowników składa rezerwacje na ten sam seans to nie dojdzie do sytuacji w której nie zajęte miejsce jest otoczone zajętymi miejscami w rzędzie

Version nie ma znaczenia w samej aplikacji, oznacza on ile razy dana rzecz została zmodyfikowana lub optymistycznie zalockowana z LockModeType.OPTIMISTIC_FORCE_INCREMENT

val name: String,

@Column(nullable = false)
val price: BigDecimal,

Choose a reason for hiding this comment

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

Plusik za BigDecimal. Pytanie, jak ograć temat gdybyśmy chcieli mieć inną walutę jak PLN?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Dodałbym pole

@Column(nullable = false)
val currency: java.util.Currency,

Hibernate potrafi to zapisać do bazy danych.

Być może tutaj jednak by było potrzeba mieć dla każdego ticket type zbiór (użył bym @ElementCollection) możliwych cen, czyli mieć powiedziane, że bilet studencki kosztuje albo 5 pln albo 1 usd. Wówczas w reservationController klient by mówił w jakiej chce zapłacić walucie i dostawał by w odpowiedzi liczbę lub error message że złą wybrał walutę.

@Embeddable
class User(
@Column(nullable = false)
@get:Pattern(regexp="[a-zA-ZAęóąśłżźćńĘÓĄŚŁŻŹĆŃ]*", message="Must contain only letters")

Choose a reason for hiding this comment

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

Ładne, a co jak będzie ktoś miał na imię Mieszko 2 ?:)

Copy link
Owner Author

Choose a reason for hiding this comment

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

Nie przewidziałem takiego imienia :) Jeśli mogą być spacje i numery to otwiera wiele pytań do tego jakie imiona są poprawne. Na przykład czy imie "P " (dwie spacje po P) jest poprawne? Musiał bym pozadawać kilka takich pytań i na podstawie odpowiedzi zbudował bym bardziej skomplikowany validator


@Transactional(rollbackFor = [Exception::class], readOnly = true)
fun getScreeningsInPeriod(
beginningOfPeriod: Instant,

Choose a reason for hiding this comment

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

A co w sytuacji, gdy seans jest w różnych strefach czasowy? :)

Copy link
Owner Author

Choose a reason for hiding this comment

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

Wydaje mi się, że będzie działać dobrze, to znaczy jak użytkownik rest api wpisze jako
beginningOfPeriod 2100-01-01T07:00:00.000%2B05:00
to ten endpoint otrzyma instant reprezentujący
2100-01-01T02:00:00.000%2B00:00
(albo inaczej reprezentujący pewien Unix time)
Innymi słowy nie interesuje tego endpointu w jakiej strefie czasowej lubi reprezentować czas użytkownik.
Zadbałem również o to by w bazie danych zapisywały się Instanty w UTC wpisując w properties
spring.jpa.properties.hibernate.jdbc.time_zone=UTC

beginningOfPeriod: Instant,
endOfPeriod: Instant,
offset: Int,
limit: Int

Choose a reason for hiding this comment

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

👍 za paginacje

)
)

val list: List<MovieScreening> = run {

Choose a reason for hiding this comment

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

Ta logika zapytań to raczej działka repozytorium..

Copy link
Owner Author

Choose a reason for hiding this comment

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

Racja


@GetMapping("/screening")
@Transactional(rollbackFor = [Exception::class], readOnly = true)
fun getScreeningInfo(dto: ScreeningInfoRequestDto): MovieScreeningAndRoomDto {

Choose a reason for hiding this comment

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

Trochę już późno :) Ale czy ja dobrze rozumiem, że w get przesyłasz payload z informacją o id screeningu?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Tak. Nie jestem pewien czy o to tutaj chodzi, natomiast można to by uprościć pisząc argument
@RequestParam screeningId: Long
i wyrzucając klasę ScreeningInfoRequestDto

path = ["/api"],
produces = ["application/json"]
)
// cors would be changed in final app

Choose a reason for hiding this comment

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

👍 Są jeszcze inne sposoby poradzenia sobie z tym, można globalnie to ograć.

Copy link

@rafa-minimal rafa-minimal left a comment

Choose a reason for hiding this comment

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

Dorzucam swoje kilka groszy ;)


@ConfigurationProperties(prefix = "custom")
data class CustomConfigProperties(
val minimalTimeBetweenBookingAndScreeningStart: Int

Choose a reason for hiding this comment

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

Nie wiadomo, czy ten czas jest w minutach czy w sekundach, jak byś to poprawił :) ?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Jedno rozwiązanie to zmiana nazwy na
minimalTimeBetweenBookingAndScreeningStartInMinutes

Lepsze rozwiązanie to by było zmienić typ tego na Duration. Wówczas mogę w properties napisać
custom.minimalTimeBetweenBookingAndScreeningStart=15m
i to tam wybieram czy to są sekundy czy minuty

initializeScreening(room3, movie1, 19)

// to make sure ids of movie screenings are accessible
entityManager.flush()

Choose a reason for hiding this comment

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

Wydaje mi się, że to nie byłoby konieczne, jeżeli metody initialize* zwracałyby obiekt z metody save

Copy link
Owner Author

@paw787878 paw787878 May 12, 2023

Choose a reason for hiding this comment

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

Korzystam z metody save() z JpaRepository i te metody initialize* zwracają wynik tej metody.

Nie zdawałem sobie sprawy, że metoda save() zwraca obiekty z ustawionym id, wiedziałem że może być z tym problem jak się używa entityManager.persist i stąd się zabezpieczyłem.

Swoją drogą, nawet jakby metoda save() zwracała obiekty z nie ustawionym id to i tak to flush() tutaj i w klasie DatabaseInitializationForTest nie było by potrzebne, ponieważ nie korzystam z wygenerowanego id, zamiast tego korzystam z referencji do java obiektów. Kiedyś odnosiłem się do id w tutaj poniżej tego flush()


@Entity
class MovieScreening(
@ManyToOne(fetch = FetchType.LAZY, optional = false)

Choose a reason for hiding this comment

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

Mam wrażenie, że wszędzie zrobiłeś leniwe ładowanie (FetchType.LAZY), czy w tej sytuacji to dobry wybór? Co nam to daje?

Copy link
Owner Author

Choose a reason for hiding this comment

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

W książce w z której uczyłem się Hibernate autor narzekał, że eager fetch type w @manytoone to zły default, że jego zdaniem defaultem powinno być lazy, stąd uczyniłem lazy defaultem. Można by sobie wyobrazić egzotyczną bazę danych w której z powodu eager loading w @manytoone załadowało by się przez przypadek całą bazę danych (bo obiekt ma dwie propertki oznaczone przez @manytoone, więc ładując obiekt ładuję dwa inne obiekty, każdy z nich może znowu ma dwa @manytoone i tak dalej)

Możliwe, że tutaj to lazy loading jest szkodliwe dla performance. Muszę doczytać czym się kierować wybierając między lazy a eager loadingiem. Wydaje mi się, że na przykład w przypadku propertki
val ticketType: TicketType
w ReservationSeat eager może być lepszy, bo gdy ładujemy reservation seat to zwykle chcemy też mieć ticket type.

val movieScreening: MovieScreening,

@Column(nullable = false)
val expirationDate: Instant,

Choose a reason for hiding this comment

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

Powiesz czemu Instant a nie np. LocalDateTime? Zdradzę, że mi akurat takie podejście się podoba, ale ciekaw jestem jakie Ty masz powody ;)

Copy link
Owner Author

Choose a reason for hiding this comment

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

Chciałem uniknąć problemów z strefami czasowymi.

Instant odpowiada pewnemu unix time (albo alternatywnie czasowi w utc), nie interesują go takie udziwnienia jak strefy czasowe.

Gdybym miał w memory obekty typu LocalDateTime to bym się musiał zastanawiać której strefie czasowej one odpowiadają. Wówczas bym przyjął, że odpowiada strefie czasowej utc, ale to wówczas bym równie dobrze (a właściwie lepiej) mógł używać Instant. A jakbym przyjął, że LocalDateTime odpowiadają strefie czasowej kraju w którym znajduje się kino to myślę, że bym otworzył puszkę Pandory, np związaną z handlowaniem zmian strefy czasowej, albo handlowaniem tego że część kin jest w jednym kraju a część w innym.

W settingsach mam linię
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
by te Instanty mi się zapisywały w utc w bazie danych

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.

4 participants