I've been playing around with MyBatis and came in ...
# server
j
I've been playing around with MyBatis and came in touch with what I can only describe as dark magic. When inserting a data class domain model in the database, the instance that was passed to the insert function is "updated" after a successful insert with the auto generated id. How is this possible when the instance is declared as val and is a data class with only vals as properties?
😂 1
🪄 1
Some code example:
Copy code
data class MyDomain(val id: Long, val some: String, val things: String)

@Mapper
interface MyMapper {
    fun insert(myDomain: MyDomain)
}

//    <insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
//        INSERT INTO my_domains (some, things) VALUES (#{some}, #{things})
//    </insert>

fun somePseudoCode(): MyDomain {
    val myDomain = MyDomain(-1L, "test", "123" )
    println(myDomain.id) // prints -1
    myMapper.insert(myDomain)
    println(myDomain.id) // prints for instance 35 (or whatever seq.nextval did produce)
    return myDomain
}
I am really fond of immutability and like to understand why this is mutating while I assumed it is immutable. 😵
e
with reflection anything\* is possible
😄 1
☝️ 2
Copy code
val aPair = Pair("a", 1)
val bPair = Pair("b", 1)
with(aPair::class.java.declaredFields.first()) {
    isAccessible = true
    set(aPair, "b")
}
aPair == bPair
j
Thanks! I was thinking of reflection, but not really familiar with it, because it is in my “dark magic” corner (meaning; I try to avoid it, because honestly, I don’t really understand it).
d
He who lives by magic, also get surprised when magic jumps up and bites him. 😂
j
Yeah, time to shed some light on the matter and understand what’s what. 😅
Does it make sense to reflect this mutability (by means of reflection) in the model? Something like this:
class MyDomain(var id: Long, var some: String, var things: String)
And/or, would it be a good idea to wrap the insert function in a function that encapsulates the mutability? So that the rest of the program can rely on immutability.
Or should I just learn reflection and when and where it is used? 😐
d
not sure how you'd model it in ibatis, but I personally prefer modelling saved and unsaved entities through the typesystem. eg. http://oneeyedmen.com/nothing-can-save-us.html
j
I've done something similar before using Hibernate; separation between the domain models and the database entities.
e
by the way
val
doesn't mean that it doesn't change, just that this isn't the mechanism for doing so. for example,
Copy code
val x: Int
    get() = kotlin.random.Random.nextInt()
of course, your case is a different since it's a
data class
, but just to be clear
j
I think I understand what you are trying to say, but still I'd argue that I would always get a
kotlin.random.Random.nextInt()
from your example. It is final, set in stone, the value is assigned and can't be changed. Maybe a better example would be assigning
class MyDomain(var id: Long, var some: String, var things: String)
to a
val
. Now it is immutable! You can't change that val to something else. But you can still change the properties of that instance; set the
some
to a new value, etc. The things inside your immutable reference are mutable in that case.
And then there was reflection and everything can be changed always. 😂
c
i think this makes ibatis not very kotlin friendly. of course you can do this:
Copy code
myMapper.insert(myDomain.copy())
👍 1
j
😲 That is actually not a bad idea. It eliminates the overhead of creating dedicated classes to model the domain and the persistence entities. And then there is the other side of the coin, it is convenient to get the generated id back. Maybe assigning a copy of the domain model to a val and using that to retrieve the id is an effective way to leverage the advantage and eliminating the disadvantage of reflection. 🤔
c
i wrote a simple orm too and it returns the entity with id. so
savedUser = repo.create(user)
thats a kotlin friendly api imo
but nowadays a lot of projects use uuids as pk so you can just create the id before saving.
j
Yeah, I like the insert/create/persist function to return a new instance of the persisted object approach. (That was actually what I was trying to do with MyBatis and then I learned that my inserted model already contained the changes 😅).
The UUID as PK approach is also good (I've used it successfully). But shouldn't the database generate the PK? Most (all?) databases are good at it. And you are more certain that no one would do something silly and create a random (or worse static(!)) UUID higher up the call chain, (where I would say it is inappropriate and could lead to problems).
c
if you generate the uuid on the client the id does not have to be nullable
d
the/a problem is that you can't tell the difference between a saved and unsaved entity.
j
It is an optimistic approach.
I guess now we are getting into the territory of what the program and database schema is for and how it is designed. I think a more event based system with an append only (insert new rows, not update existing rows) approach could work really well with client generated UUIDs. In a more traditional relational database schema, I would think this optimistic approach can be challenging to keep consistent.
c
the/a problem is that you can’t tell the difference between a saved and unsaved entity. 
i found that in practice my code always knows if an entity is persisted or not and in usually don’t need a createOrUpdate method
d
without the typesystem, there is no way to guarantee that is the case. would be interested to know of patterns that you're using to help with that
c
maybe my usecases are just simpler than yours. my plan is to tackle it when it comes up. but if you have an example where its needed I’m always interested
if needed i can always use a custom PK class that tracks if the object is already persisted
j
YAGNI (yet) works. 😃
I have to say I am intrigued by your approach. Could be fun to play around with this idea. 🙂
d
For instance, by using the typesystem we can have a repo which is:
Copy code
interface Repo {
	fun add(new: Item<Nothing?): Item<PK>
	fun get(new: PK): Item<PK>?
	fun all(): List<Item<PK>>
}
This means that you can't try to insert an existing record, and that you can guarantee that all records with a PK have been saved. It centralises the allocation of the PKs (which won't always be UUIDS). We can also enforce the flow of the data through the system in a typesafe way.
Some details this blog post from @dmcg http://oneeyedmen.com/nothing-can-save-us.html
c
yeah its definately an interesting idea, but is it worth giving up on client created uuids? (my orm still uses int keys, just wondering about what direction i should take for uuid key support)
also what about erasure? isnt that a problem?
d
in general we wouldn't ever trust client created keys - the risk for messing up is too great.
we would treat it the same as an external client creating a key.
c
ok but we’re not talking about a rest client supplying an id here, are we? i mean the server assigns an id, either in java code or it delegates it to the postgres server. both seems equally safe to me
and the upside of doing it in server code is less logic in the database, and easier to test
d
for us, it's fine to do the generation in server code, but that code should be was in the class doing the actual persistence.
c
so not as default value of the data class
and the repo class assigns it, either via the database or itself
d
it's noone else's business to assign it - then if the rules for the assignment change it only has to be changed in one place
👍 1
c
but if the id is assigned at instantiation via a default parameter, the factory method for the default parameter will also be part of my library so i can just change it there.
btw an pattern i started using for data classes with nullable PKs is to make the id the last parameter instead of the first. then you can just omit it
d
there is still no way to enforce via the type system. and you're still trusting external ID generation. YMMV, but for us it turned out to be the preferable approach 🙂
c
btw what orm are you using?
d
I'm not a fan of ORMs, so none. 🙂
prefer typesafe SQL - Jooq or Exposed or similar
c
ah makes sense.
I’m not a huge fan of exposed so i wrote a simple orm. but the api is very much in flux, but would love to hear your thoughts: https://github.com/christophsturm/the.orm
actually its not really an orm yet because it does not fetch relations