https://kotlinlang.org logo
#android-architecture
Title
# android-architecture
u

ursus

05/02/2019, 5:59 AM
+ This presumes SELECT * projections..How about partial projections? Would you rather have scoped object of only user name and imagr url? Or full User object with name and url filled and rest of fields null?
g

ghedeon

05/02/2019, 9:45 AM
normally yes, data, domain and ui models and mappers between them. Scoped objects for partial projections.
u

ursus

05/02/2019, 4:46 PM
hmm not sure If I get you. I you have a domain object Foo, how is it then mapped from partial partialDbFoo? are the rest of fields null?
d

dewildte

05/02/2019, 7:59 PM
MessageDTO come from a network call. It can then be mapped into a CachedMessage object to be stored into the DB. The CachedMessage can then be mapped into a DomainMessage if some domain related things need to be done with it. Alternatively the CachedMessage can be mapped to a RenderableMessage for the UI to consume. Thinking of each part of your application as separate modules with different concerns helps.
u

ursus

05/02/2019, 8:00 PM
@dewildte this is all clear and good. Where it gets problematic for me is 1) joins 2) partial projections of selects
d

dewildte

05/02/2019, 8:01 PM
Ok, why exactly?
u

ursus

05/02/2019, 8:01 PM
which implies the domain object needs to either have variations, or nullable fields
or do select * everyhere, no?
imma be concrete so we dont just ramble, I have accounts, each account has channels, each channel has messages (like slack)
d

dewildte

05/02/2019, 8:02 PM
ok
u

ursus

05/02/2019, 8:03 PM
so when I display messages in channel ui; the select is basically messages join channel
- when I display notification I need account too; so message join channel join account where message id = ?
so; how would your domain object for this look? nullable account?
d

dewildte

05/02/2019, 8:06 PM
Why can't the messages objects contain the channel ids, and account ids as part of thier state?
Slack looks like they would have all this data attached to a Message.
Am I missing your point here?
u

ursus

05/02/2019, 8:10 PM
well they can, not sure what r u getting at?
d

dewildte

05/02/2019, 8:11 PM
FRom what I understand you are trying the query for messages but need to join them to another domain object like an Account object correct?
u

ursus

05/02/2019, 8:12 PM
true
d

dewildte

05/02/2019, 8:12 PM
Ok, so the return object from the Join is it's own data class.
It has the message(s) as one field and the account as another.
u

ursus

05/02/2019, 8:12 PM
what I am struggling with is the domain layer/ domain objects, how should they look; since from what I understand, there should be only one Message in the domain?
okay so youre saying this? (domain layer)
Copy code
data class Message(...)
data class Account(..)
data class MessageWithAccount(message, account) ?
d

dewildte

05/02/2019, 8:16 PM
From a query perspective yes
u

ursus

05/02/2019, 8:17 PM
what query? this is domain layer im talking about
d

dewildte

05/02/2019, 8:17 PM
You may not be using Room as your database but they have good documentation on doing this kind of thing.
u

ursus

05/02/2019, 8:18 PM
no they dont, this is different issue, youre talking data layer, thats no problem
domain is problematic for me; question is if there should be only one Message type in domain; and how does it related to data layer from joined queries; and how does it look like mapped from partial projections in data layer
getting joins mapped onto data classes is no problem, room and sqldelight do this automatically; after that is what Im trying to ask
unless you mix your data layer objects and domain layer objects
d

dewildte

05/02/2019, 8:21 PM
After getting the data from the CachedMessage from the DB you will then map it into the DomainMessage.
DomainMessage contains all the logic required for doing message things.
u

ursus

05/02/2019, 8:22 PM
thats theory fine,; please how would you add account to this?
composition?
d

dewildte

05/02/2019, 8:22 PM
Account is the same
Messages hold in them the account id
The Account does not need to know about messages.
u

ursus

05/02/2019, 8:23 PM
yes it doesnt; im asking how do you get the account in the domain layer for a given message
d

dewildte

05/02/2019, 8:24 PM
You find it by using the id attached to the message.
u

ursus

05/02/2019, 8:24 PM
okay so youre doing 2 queries; one to get message; one to get account?
d

dewildte

05/02/2019, 8:25 PM
Network or DB queries?
u

ursus

05/02/2019, 8:26 PM
okay use case for example is this: sync happens, somebody finds out message id = 123 is new; show notifictaion for it; so you query db = you get cachedMessage
d

dewildte

05/02/2019, 8:26 PM
Get the message first and then get the account using the account id attached to the message.
u

ursus

05/02/2019, 8:26 PM
how will you get coresponding Channel and Account objects? You need both (or rather all three) to display a notification
okay that implies you going twice/three times to the db, am I correct?
d

dewildte

05/02/2019, 8:27 PM
All of that is meta data attached to the message object.
Copy code
Message(val accountId: String, val channelId: String, ...)
u

ursus

05/02/2019, 8:28 PM
is this a DomainMessage?
d

dewildte

05/02/2019, 8:28 PM
Yes sir
And the DB will have this data to
u

ursus

05/02/2019, 8:29 PM
fine; how do you get DomainAccount and DomainChannel -- is it by doing 2 queries now? (and mapping CachedAccount to DomainAccount + same for channel)
d

dewildte

05/02/2019, 8:29 PM
If the Message has no information related to where it originated then you have a problem.
u

ursus

05/02/2019, 8:30 PM
Youre missing my point
I think youre suggesting I ran 3 queries instead of 1 join query
no?
d

dewildte

05/02/2019, 8:31 PM
You will need to make 2
one for the message
the next for the joined account and channel
Provided the API (data base or network) can return the joined data.
u

ursus

05/02/2019, 8:32 PM
honestly that is bad
why would you run a select from messages where id =? and then select from channel where id=? and same for account
if you can run a join?
Imagine you need a list of this .. will you do N*3 queries instead of 1?
d

dewildte

05/02/2019, 8:35 PM
Look
All I am saying is that to do 1 query you need all the Ids
u

ursus

05/02/2019, 8:36 PM
which I have, yes
d

dewildte

05/02/2019, 8:36 PM
If you can devise a way to get all the ids of the objects you require then you can get the objects in 1 shot.
u

ursus

05/02/2019, 8:37 PM
yes message table has channel id column, and channel has account id column
sorry if I wasnt clear on that earlier
d

dewildte

05/02/2019, 8:38 PM
Take the ids and make a single query for all the objects at once.
You get a data class containing all the 3 things.
u

ursus

05/02/2019, 8:38 PM
yes, all is good, im asking now how should the domain objects look like
d

dewildte

05/02/2019, 8:38 PM
Then you can map them all into the things you need.
u

ursus

05/02/2019, 8:39 PM
and how do you attach stuff, should there be composition of should message have val channel, val account
d

dewildte

05/02/2019, 8:39 PM
The domain object should be things you can ask to do work.
u

ursus

05/02/2019, 8:40 PM
yes I know; im asking what would be the signature of such method
d

dewildte

05/02/2019, 8:40 PM
If a message can not have empty text then that is encoded in the object itself.
u

ursus

05/02/2019, 8:40 PM
if it needs domain message, domain channel, domain account
d

dewildte

05/02/2019, 8:40 PM
A mapping method?
u

ursus

05/02/2019, 8:41 PM
no; something in the domain, for example
NotificationDisplayer.displayNotification( ...???... )
if it needs message and its channel and its account
d

dewildte

05/02/2019, 8:41 PM
give it all the data it needs to do it's job and no more.
u

ursus

05/02/2019, 8:42 PM
omg .. should there be a 1 param in that function of some joined data class or 3 params?
d

dewildte

05/02/2019, 8:43 PM
if it only needs a partial amount of data from each object then you extract i'ts requirements into a neat little package and had it to the function.
u

ursus

05/02/2019, 8:43 PM
not sure what do you mean; like another type of lets say DisplayNotificationInput?
d

dewildte

05/02/2019, 8:44 PM
Sure
u

ursus

05/02/2019, 8:44 PM
what is the point of domain objects then of every function in domain will have its own input type?
d

dewildte

05/02/2019, 8:44 PM
If the notification method requires more then 3 parameters then give a data class containing those things.
Displaying a notification is not part of the domain layer
It's a UI concern
and the UI can get UI data.
u

ursus

05/02/2019, 8:45 PM
I dont think so
d

dewildte

05/02/2019, 8:45 PM
That's your opinion
u

ursus

05/02/2019, 8:46 PM
okay forget that, pick some other function you consider in the domain
d

dewildte

05/02/2019, 8:46 PM
But in a large complex application you will run into problems if you do not separate things.
u

ursus

05/02/2019, 8:47 PM
huh, Im trying to, you just consinder it UI, I dont, but that is not my point
my point is -- you have a repository that returns cached message + cached channel + cached account
d

dewildte

05/02/2019, 8:47 PM
yes
u

ursus

05/02/2019, 8:47 PM
then you have some domain interactor thing whatever you call it,
and whats the return type for such mapping
d

dewildte

05/02/2019, 8:48 PM
The interactor is not a domain object.
u

ursus

05/02/2019, 8:48 PM
a Triple<DMessage, DChannel, DAccount>? or DMessage( .... , dChannel, dAccount) or MessageWithChannelAndAccount(dMessage, dChannel, dAccount)
this is my question
d

dewildte

05/02/2019, 8:49 PM
it is a thing that controls the interaction between domain objects and other infrastructure to accomplish a task for the user.
The third one is returned from the query.
Are you aware of GRASP?
u

ursus

05/02/2019, 8:50 PM
yes, and I dont you understand me still
d

dewildte

05/02/2019, 8:50 PM
General Responsibility Assignment Software Patterns
u

ursus

05/02/2019, 8:50 PM
im asking if you have a composite types in the domain layer
d

dewildte

05/02/2019, 8:50 PM
No you do not
They are separate objects
Sometimes even there own modules.
u

ursus

05/02/2019, 8:51 PM
okay fine, how do you do returns then, tuples?
d

dewildte

05/02/2019, 8:51 PM
data classes
u

ursus

05/02/2019, 8:51 PM
..thats a composite type
d

dewildte

05/02/2019, 8:52 PM
IT is not a composite
It is a data container
A composite is a different thing
u

ursus

05/02/2019, 8:52 PM
a composite is composed of parts; in this instance Im thinking a container to carry around message and its channel and its account
d

dewildte

05/02/2019, 8:53 PM
Ok
u

ursus

05/02/2019, 8:54 PM
what would be a domain function for you .. MessageSender.sendMessage ?
Copy code
fun foo() {
	val messageAndChannelAndAccount messageRepository.messageById(...id)
	val ???? = messageDomainMapper(messageAndChannelAndAccount)

	messageSender.sendMessage(needs domainMessage, domainChannel, domainAccount)
}
d

dewildte

05/02/2019, 8:56 PM
I send the message a command and it returns me an error or a list of events that describe what changed. The Message would have a
handle(command: MessageCommand): Either<MessageFailure, List<MessageEvent>>
function. That is a domain function.
That's just one example.
Another domain related thing would be validation of potential new messages.
u

ursus

05/02/2019, 8:58 PM
okay fine, thats besides my point
im asking how do you get data to it from data layer
d

dewildte

05/02/2019, 8:58 PM
If I am doing CRUD then the Message is just a class that holds some data.
u

ursus

05/02/2019, 8:58 PM
please see my pseudo code, the
???
is what im asking
d

dewildte

05/02/2019, 8:58 PM
The real domain work could be done by a
MessageValidator
And other classes.
oh
u

ursus

05/02/2019, 9:00 PM
yes, thats all after it, the ??? is my source question
d

dewildte

05/02/2019, 9:03 PM
Copy code
fun foo() {
    val messageAndChannelAndAccount = messageRepository.messageById(messageId, accountId, channelId)

   val mappedClasses = messageDomainMapper(messageAndChannelAndAccount)

    messageSender.sendMessage(mappedClasses)
}
My question is why messageSender need all three classes. Is it an interactor?
Does that help?
u

ursus

05/02/2019, 9:13 PM
I made it up, lets just say it needs because of backend api
heh .. and what is MappedClasses?
I thought there should be only DomainMessage, DomainChannel, DomainAccount in the domain layer as data types
d

dewildte

05/02/2019, 9:14 PM
That's correct
u

ursus

05/02/2019, 9:15 PM
then what is MappedClasses...?
d

dewildte

05/02/2019, 9:15 PM
So the query gets you the composite the the cached classes ok?
u

ursus

05/02/2019, 9:15 PM
yes its a data class of DbMessage, DbChannel, DbAccount
d

dewildte

05/02/2019, 9:16 PM
The mapper will take the composite and return you another composite containing the Domain classes.
You then pass the domain composite to the MessageSender
u

ursus

05/02/2019, 9:16 PM
yes, that was what I written before
and you said no composites in the domain
d

dewildte

05/02/2019, 9:18 PM
The composite can be removed if it's not needed by the messageSender
the message sender can get all 3 classes put into it's function as parameters if needed.
u

ursus

05/02/2019, 9:19 PM
okay so lets have the composite
data class DomainMessageAndDomainChannelAndDomainAccount(val message: DomainMessage, val channel: DomainChannel, val account: DomainAccount)
so this is the ONLY type that should be used when a consumer in the domain wants all three objects? or rather the plumbing should use, before destructuring it into 3 params of the consumer function?
d

dewildte

05/02/2019, 9:20 PM
If you have a function that takes all 3 of those things then you can define this as the only parameter if you like.
u

ursus

05/02/2019, 9:20 PM
sure thats besides it, im asking how to pass the triple around
d

dewildte

05/02/2019, 9:21 PM
You already defined that
u

ursus

05/02/2019, 9:21 PM
cool
d

dewildte

05/02/2019, 9:21 PM
You just pasted the triple
u

ursus

05/02/2019, 9:21 PM
yes thats qnaswer to my question
d

dewildte

05/02/2019, 9:21 PM
LOL!!!!!
wow 157 messages later
u

ursus

05/02/2019, 9:21 PM
-- what do you do about partial projections?
how do you map those into domain objects?
presuming the partial projection has less fields queried than target domain objet
d

dewildte

05/02/2019, 9:22 PM
You cant really have a partial of a domain class.
That is just some data.
Let me give you an example
A Message has attachments and some text ok?
but you only have the attachments.
Then you make a new message and set its attachments to the ones you possesses and it's text to ""
Now you have a whole message class
u

ursus

05/02/2019, 9:24 PM
well thats just easy because of its a string and emptyList()
what if its some type
okay so that means you always do SELECT *?
d

dewildte

05/02/2019, 9:25 PM
if you can not create a default empty implementation of the type you can just make that type nullable
Or an Option<T>/Maybe<T>
yes
SELECT *
get the data you can to hydrate a domain Message class.
u

ursus

05/02/2019, 9:27 PM
okay and if that becomes a perf problem?
default values?
d

dewildte

05/02/2019, 9:27 PM
what about default values?
u

ursus

05/02/2019, 9:28 PM
lets say your domain message is 50 fields because of some use case
but sometimes you only need id and text
Copy code
val messageIdAndText = messRepo...
val domainMessage = DomainMessage(messageIdAndText.id, messageIdAndText.text, ....now what)
d

dewildte

05/02/2019, 9:29 PM
SELECT * FROM message_id WHERE id = "blarg"
join that with the text
Something like that
My SQL is rusty
u

ursus

05/02/2019, 9:31 PM
so you dont care about perf difference of querying 2 columns vs 50 via * ?
d

dewildte

05/02/2019, 9:31 PM
Don't get a whole message object if you only need 2 fields.
Get the 2 fields
u

ursus

05/02/2019, 9:32 PM
fine, but how does that get mapped into domain .. rest of vals on DomainMEssage with null or default values?
or should there be some hierarchy od domain message variations
d

dewildte

05/02/2019, 9:32 PM
Both work
It depends on your use case
u

ursus

05/02/2019, 9:33 PM
well that was my original question, if there should be only 1 Message type in domain or can there be more
and if they should be related somehow via interfaces
d

dewildte

05/02/2019, 9:34 PM
MMS Message is not SMS Message but both could have id and text.
u

ursus

05/02/2019, 9:36 PM
that sure, im meaning more like
Copy code
interface SimpleMessage {
   id, text
}

interface FullMessage : SimpleMessage {
    channel, account, whatever
}
d

dewildte

05/02/2019, 9:36 PM
If you have a function that takes a Message that has an id and text but the function does not care about whether it is MMS or SMS then you can have an IMessage interface.
Yes
That's the ticket.
u

ursus

05/02/2019, 9:37 PM
thats different messages, im thinking more of like scoping the same data
d

dewildte

05/02/2019, 9:37 PM
An interface does that
u

ursus

05/02/2019, 9:37 PM
didnt know if that is valid in CLEAN
d

dewildte

05/02/2019, 9:37 PM
It is valid in SOLID
lol
u

ursus

05/02/2019, 9:38 PM
alright acronyms
one more thing I want to ask
d

dewildte

05/02/2019, 9:38 PM
The L in SOLID is what we are talking about here.
u

ursus

05/02/2019, 9:38 PM
sure, then again you can pass data layer all the way to ui
d

dewildte

05/02/2019, 9:39 PM
Liskov's Substitution Principal fits the bill here.
Hmm
I would not go that far.
u

ursus

05/02/2019, 9:39 PM
I know how polymorphism works lol, im talking about architecture and layering
what I wanted to ask is .. this all presumes mapping like this api message -> db message -> domain message
d

dewildte

05/02/2019, 9:40 PM
A common interface is fine
The domain defines the interface though.
u

ursus

05/02/2019, 9:40 PM
that is basically for displaying the chat messages
now I need to search messges -- search is a backend feature, I dont have all messages locally
d

dewildte

05/02/2019, 9:41 PM
ok
u

ursus

05/02/2019, 9:41 PM
so the mapping should go api message -> domain message directly?
I presume yet, but, that duplicates the mappers
d

dewildte

05/02/2019, 9:41 PM
If you want to bypass the DB then yes
u

ursus

05/02/2019, 9:41 PM
and that can be error prone duplication
thats just the way it is and should write unit tests?
d

dewildte

05/02/2019, 9:42 PM
RemoteMessage(): IMessage
maps to
DomainMessage(): IMessage
No
u

ursus

05/02/2019, 9:43 PM
huh why? isnt RemoteMEssage(=ApiMessage) part of data layer?
d

dewildte

05/02/2019, 9:43 PM
The reason we do the mapping is to make sure the data from the outside world is clean for our domain.
u

ursus

05/02/2019, 9:44 PM
yes, why do you have the interface there then? IMessage is domain layer no?
d

dewildte

05/02/2019, 9:45 PM
Can we do a call?
Maybe if I spoke to you in person it would help
u

ursus

05/02/2019, 9:46 PM
sorry cant call right now
d

dewildte

05/02/2019, 9:46 PM
ok
No worries
u

ursus

05/02/2019, 9:46 PM
I mean in general im good
but what worries me is
is mapping from api msg to domain msg being bugly different from api -> db -> domain msg
unless, you create a dummy db message just for pourpose of mapping
d

dewildte

05/02/2019, 9:48 PM
ApiMessage ->UIMessage
user modifies the message ok?
UIMessage -> DomainMessage
u

ursus

05/02/2019, 9:49 PM
not sure what you mean, Im talking about search, where search is remote
d

dewildte

05/02/2019, 9:49 PM
Ask the now DomainMessage to handle the users command
I understand
u

ursus

05/02/2019, 9:50 PM
and my issue is I worry about ApiMessage -> DomainMessage mapper being different from ApiMessage -> DbMessage -> DomainMessage
d

dewildte

05/02/2019, 9:50 PM
If you just want to grab the message from remote and show it on the screen then do api to ui mapping
u

ursus

05/02/2019, 9:50 PM
or even UiMessage, doesnt matter, same problem
of skipping mapping layers
and how to keep the mappers consistent
and the question is if I should create dummy layer objects just for purposes of mapping, so I have a single pipeline
d

dewildte

05/02/2019, 9:52 PM
Say all messages must have 3 things
accountId, channelId, and id
then domain makes an interface
Copy code
interface IMessage {
 val id: String
 val accountId: String
 val channelId: String
}
u

ursus

05/02/2019, 9:54 PM
ok
d

dewildte

05/02/2019, 9:55 PM
everything that is considered to be a message implements that interface.
When the message hits the domain from anywhere it will be ok because everything conforms to that interface.
Everything has a dependency on the domain
u

ursus

05/02/2019, 9:57 PM
that is probably fine, what im getting at is forgetting some step as in to preprocess message text for mentions for example
d

dewildte

05/02/2019, 9:57 PM
and the domain determines the rules around the data
you could build that into the constructor of the classes?
u

ursus

05/02/2019, 9:58 PM
or maybe mapping enums .. lets say ApiMessage has some pseudo type on it of "foo", "bar", etc; DbMessage has the same enum just mapped to 0 1 itnegers; and DomainMessage needs real enum type of Type.FOO, Type.BAR
d

dewildte

05/02/2019, 9:59 PM
Copy code
init {
 doPreprocess
}
u

ursus

05/02/2019, 9:59 PM
so now you have two mappers for this "foo" -> Type.FOO; and "foo" -> 0 -> Type.FOO
and this is what I worry about getting out of sync
d

dewildte

05/02/2019, 10:01 PM
You only have so many solutions
u

ursus

05/02/2019, 10:01 PM
well yes, im asking about your opinion .. you can craete dummy dbMessage for the first case, then its always correct but you create garbage
or 2nd options: unit tests
d

dewildte

05/02/2019, 10:02 PM
You either embed the logic in the constructors, intercept the API DTO and do the pre-processing there, or shove it in the mappers.
Unit test is not an option
it is a requirement
lol
u

ursus

05/02/2019, 10:04 PM
well its either compiler or tests as enforcement; id much prefere compiler
d

dewildte

05/02/2019, 10:04 PM
You could use sealed classes and when expressions to ensure new types of messages added to each layer are handled by the mappers.
I did that in an SDK I built
When I added a new DTO it would have to inherit from the sealed class. Then my code would not run with out updating the mapper.
sealed class Message
I have to go
But I am sure you will make the right choices.
u

ursus

05/02/2019, 10:08 PM
okay thats a lot for your help
d

dewildte

05/02/2019, 10:09 PM

https://youtu.be/sOaS83Ir8Ckâ–¾

That talk might help you
I build my stuff this way and it has helped me a lot.
u

ursus

05/02/2019, 10:14 PM
what is it about, about my mappers sync issue?
d

dewildte

05/02/2019, 10:16 PM
Not directly, it is about where everything in your project should go and why.
Indirectly it might help you make the choices you need to
u

ursus

05/02/2019, 10:23 PM
k k thanks