https://kotlinlang.org logo
Title
b

Bernhard

10/10/2018, 3:19 PM
how do you feel about having different objects for writing and reading database objects? Writing an object will for instance not require an id whereas querying an object will always have one. Otherwise I have to add null checks everywhere.
d

diesieben07

10/10/2018, 3:21 PM
How about this?
class DbEntry<ID : UUID?>(val id: ID, val name: String)

typealias SavedDbEntry = DbEntry<UUID>
typealias UnsavedDbEntry = DbEntry<Nothing?>
UnsavedDbEntry
can only hold
null
for
id
,
SavedDbEntry
can only hold non-null
UUID
.
And you can use
DbEntry<*>
to refer to either.
b

Bernhard

10/10/2018, 3:24 PM
neat trick however I'm also using this to generate a swagger api
and some attributes are unfortunately de-normalized and can't be written
I suppose the Nothing will not translate cleanly to java
it will fail at runtime, right?
d

diesieben07

10/10/2018, 3:25 PM
No. It's
Nothing?
, not
Nothing
.
And
DbEntry
(the only thing visible from a Java standpoint), has a field (and getter) of type
UUID
with
@Nullable
.
b

Bernhard

10/10/2018, 3:26 PM
what happens if the user sets it in Java though
d

diesieben07

10/10/2018, 3:26 PM
They can set it to null
As always in java
b

Bernhard

10/10/2018, 3:27 PM
and also to a value?
d

diesieben07

10/10/2018, 3:27 PM
Sure
Java sees
UUID
, which to it's mind can be null.
Java does not see those typealiases at all
t

tddmonkey

10/10/2018, 3:56 PM
Is there a reason you don’t want to just use different objects for input vs output?
My experience is that it solves way more problems than it causes. Not only does it help your
id
vs
no-id
issue here, it helps you be sure that adding an attribute on one (save, read etc.) path doesn’t have an unintended affect on another path
b

Bernhard

10/11/2018, 6:57 AM
@tddmonkey that's what I was thinking as well. Just wanted to get some feedback since I need some boilerplate code for that which converts between these objects (e.g. fetch object from db, update value and save it)
ok, I think I found a solution for that as well: I will just create an interface for both objects and require that for creating an object.
data class User(val id: String, override val name: String) : SavableUser

data class CreateUser(override val name: String) : SavableUser

interface SavableUser {
    val name: String
}
in java I probably would have gone for inheritance but interface feel like an even neater solution
d

diesieben07

10/11/2018, 7:36 AM
I am not sure why that is better than my typealias solution
t

tddmonkey

10/11/2018, 8:16 AM
That feels like it solves a single issue of the type - what about when the difference is larger?
d

diesieben07

10/11/2018, 8:17 AM
I am not sure I follow you
t

tddmonkey

10/11/2018, 8:18 AM
When the representations differ by more than just having an ID or not
d

diesieben07

10/11/2018, 8:19 AM
You could add more type parameters. The problem with having two classes is that there is no easy way to convert between them without spelling out all attributes again.
t

tddmonkey

10/11/2018, 8:20 AM
ymmv. tbh I use this technique a lot and don’t usually find myself converting between them so much
Even if I did, I think I’d rather have that than the risk of unwarranted changes to a flow I’m not working on
d

diesieben07

10/11/2018, 8:22 AM
don’t usually find myself converting between them so much
Not sure how you are doing this. You will need to write a separate
saveToDb
function for every type you have...
t

tddmonkey

10/11/2018, 8:26 AM
The majority of my work is in RESTful BE services, so there will be a
GET
endpoint for a client to retrieve data, then a
PUT/POST
endpoint to update data. There’s no read-update-write cycle inside my service
d

diesieben07

10/11/2018, 8:28 AM
Well, that is a totally different scenario. The original question was about database objects.
t

tddmonkey

10/11/2018, 8:29 AM
I don’t see how - I’m still reading and writing to a database
how do you feel about having different objects for writing and reading database objects? Writing an object will for instance not require an id whereas querying an object will always have one. Otherwise I have to add null checks everywhere.
. Nothing in there suggests that isn’t/can’t be a stateless RESTful service
d

diesieben07

10/11/2018, 8:31 AM
The way I understood the question:
val unsaved = UnsavedEntity(... data ...)
// unsaved.id does not exist / is null
val saved: SavedEntity = db.save(unsaved)
println(saved.id) // guaranteed to be not null
Inside
db.save
you need to go from
UnsavedEntity
to
SavedEntity
.
If those are different classes, that process is tedious and needs to be done separately for every entity type.
t

tddmonkey

10/11/2018, 8:38 AM
If you need to return a saved representation straight away, perhaps. I typically don’t see that though. I’m coming from the point of view of a more CQRS approach where the representations can happily differ and there’s no need to treat them the same if they don’t have to be. If your saved object is truly and always going to be the same as a new object, but with an ID then yeah, code sharing is probably the right way to go. My experience is typically that they have a tendency to diverge, and when that happens I don’t want to try and shoehorn a representation in that doesn’t quite fit, just because I used the same class.
The OPs question was “how do you feel about having different objects for writing and reading database objects?“. I was just stating it’s something I do regularly and find it not only useful, but potentially solving other classes (heh) of problems
d

diesieben07

10/11/2018, 8:40 AM
I agree with you, it seems we just understood the question differently.
t

tddmonkey

10/11/2018, 8:46 AM
Out of interest - why would you go the
typealias
route instead of having a (potentially reusable) container object to represent the saved state and using composition? Say something like: class SavedItem<T: Any>(val id: UUID, val item: T) Then you either use
User
or
Saved<User>
as necessary in the right place?
d

diesieben07

10/11/2018, 8:49 AM
That would also work, but has the disadvantage of having to do another hop "all the time" (
user.item.name
instead of
user.name
).
t

tddmonkey

10/11/2018, 8:53 AM
Inheritance + delegation?
d

diesieben07

10/11/2018, 8:53 AM
Can't delegate to a
data class
😄
t

tddmonkey

10/11/2018, 8:56 AM
Hmm, yeah, messy
d

diesieben07

10/11/2018, 8:56 AM
Database access is always messy... It's one of my constant pain points in any language.
t

tddmonkey

10/11/2018, 8:57 AM
tbh I try to architect my systems to avoid as much of that mess as possible