This message was deleted.
# community-support
s
This message was deleted.
m
There are some options but all of them will require to "stub" your engineering API at some point: 1. only call your engineering from
src/debug/kotlin/**
2. expose an interface that you load using a ServiceLoader or some other reflection technique (I think log4j does this) 3. expose different artifacts
engineering
,
engineering::noop
(what you're currently doing)
thank you 1
You could also always call into
engineering
and use R8 to remove the calls in release builds, like you would remove calls to
Log.d
s
1. I explicitly was trying to do something other than this because of the following case: I was loading a koin module from that
engineering
module with
loadKoinModules
exposing some interface impls which would then be taken and used here, but this resulted in koin not loading those at the same time, so the things provided from
engineering
were not picked up in time😅 So I wanted to move the engineering module in my main
startKoin{}
to load them at the same time like I now do here. So since that is called from scr/main I have to have access to it, and have it do nothing in prod. I asked about this here to see if I could fix it on the koin part though. 2. Ngl this sounds like some magic, I don’t even know where I’d start with smth like that so I’m thinking I’ll skip if I got other options 😄 3. Yeap, this is what I am doing right now. 4. The r8 option does sound very interesting, but could I then “remove” the entire module using r8? What about the call here, what would this then do 🤔 I understand with Log.d it can just not call it, but here since I’m constructing a list, how would r8 work to make this work? I’d guess it’d have to look somewhat different.
m
2. is basically doing 3 dependencies instead of 2: •
engineering-api
is the one that you depend on in your app and defines an interface •
engineering-impl
is the one you add to
"debugImplementation"
engineering-noop
is the one you add to
"releaseImplemenation"
And then retrieve the implementation by reflection. This is not that different from 3 except that you get a bit more tooling because there is now a "contract" of what
"engineering-api"
is and IntelliJ will tell you what methods are missing in
"engineering-noop"
to satisfy the contract
since I’m constructing a list, how would r8 work to make this work?
Not super familiar with koin but I guess you could do this:
Copy code
val myModules = mutableListOf(
  ...
)
appenEngineeringModules(myModules)
modules(myModules)
And remove
appendEngineeringModules
with R8
You could also register a different transform that modifies the bytecode with
asm
if you wanted to... Certainly a lot of trouble and will start to feel like magic to someone not familiar with the codebase though...
🧙 1
All in all, my favourite solution would be 2. Sure it's some boilerplate but it works well with the IDE and it's easy to understand
s
Yeah the asm idea feels like magic indeed, not quite familiar enough to try that I think. The API approach does sound very nice indeed, the tooling and clear understandability wins sound like what I'd prefer. I think I get all of what you're saying until you mentioned the reflection part. Do you have any sample in mind which does something like that which I could look into?
m
Because the
engineering-api
module will be an interface, it will not have any constructor. The constructor is what you need to get by reflection.
I guess if you use the same name in both implementation, it'd work without reflection though
Reflection is useful if you want to allow loading implementations at runtime but looks like this is not your case as you'll have both implementations available at compile time
👍 1
s
Yeap, awesome thanks a lot for your help! I think I mostly get it, just need to spend some time with it when I'm back on a computer 😅 May post an update here once I try it out 😊
👍 1
Okay got some time to try this out now, I am not sure I got it tbh, this is the diff of me trying to apply this idea. In the end I am simply using the Impl version in my :app module, not resolving anything from the -api module. But I guess I am getting the benefit in the :engineering-noop and the :engineering where I need to implement the same interface and I need to give something back. But again, I do need to remember and make an impl I guess. I feel like in the end I didn’t quite get it 🤔
m
Yea I guess because you have everything at compile time there's not a huge difference in adding the interface. I kind of like it because it's easier to navigate back and forth between implementations
Also doesn't require touching the AS "build variant" setting. You can always execute
:engineering-noop:build
and check whether something's missing without having to compile your app in release mode
thank you 1
s
Alright then I did get it 😅 I can totally see the benefits actually, jumping to the impl instead of having to just search anywhere in the project for the necessary string. +1 for this one! For doing
./gradlew :engineering-noop:build
I guess I get this benefit after I make sure I am implementing that interface of course, I could in theory declare smth on an -api module and forget to do it on the -noop one and it’d still build, but I guess we can’t have it all 😄 Thanks so much for talking me through this btw, I really appreciate it! As a side note, I’d still be curious to see how one would do this with reflection as you hinted before, not for my use case since I am good with what I’ve done now already, but for educational purposes 😄 If you do have some sample in mind I’d love to see it, if not no worries, I look for smth myself.
Also I’m kinda worried of introducing all these new modules and scaring my teammates off 😄 We had a single module project and I’ve been trying to start this process a bit. We’re

here

atm and this PR of mine would make it look like

this

so I’m trying to do everything in a way that will make as much sense as possible 😄
😅 1
m
If you do have some sample in mind I’d love to see it, if not no worries, I look for smth myself.
I don't have a lot of samples at hand but I'm pretty sure this is how log4j does it (but I never looked really deep as that codebase is quite the beast): https://logging.apache.org/log4j/log4j-2.3.2/manual/extending.html All in all, it relies on a resource file with a fixed name under
META-INF
that contains the name of the implementation to load reflectively. This allows to change the logging at runtime to something that logs to a remote server or to /dev/null depending the use cases
thank you 1
Seems to be used quite a bunch in Spring Boot too: https://www.baeldung.com/java-spi
Ah, there's a nice article from Nicolas Frankel that gives a high level overview https://dzone.com/articles/java-service-loader-vs-spring-factories-loader
"inversion of control" is the key term there. There's a lot of words in these links but it's always the same concept of a resource file pointing to the implementation to load reflectively
👍 1
s
Ooh bunch of reading material 📚 Thanks for this!
m
Ahah sure thing 🙂 . I wouldn't bother too much about the details because each framework comes with its own jargon and way to name things. The important thing is that you can leave it to the runtime to choose a specific implementation with a carefully crafted resource file and some magical reflective constructor invocation