Open surbhiia opened 2 weeks ago
Currently, services are housed under core -> internal -> services
Changes proposal:
Preferences can be made a generic utility class instead of being a service. It can be moved from core -> internal -> services -> Preferences.java
to core -> Preferences.java
or core -> common -> Preferences.java
. It's creation can be done directly from the two places from where ServiceManager.initialize
is called - OpentelemetryRumBuilder.build() and OpentelemetryRumBuilder when Sdk is Pre Configured (This should probably be moved inside the SdkPreConfiguredRumBuilder class itself). Preferences.java can provide a singleton Preferences object wherever needed.
CacheStorage: Same as above. It can also be moved to core -> internal -> features -> persistence where DiskManager
lives, if we don't perceive it would be required in common module for wider usage.
ServiceManager : ServiceManger
, ServiceManagerImpl
can be housed in a new module under root: service - > service-manager. I think we can expose ServiceManger
and ServiceManagerImpl
as well for the users who want all 3 services. The android-instrumentation module proposed in the linked issue will depend on this as InstallationContext needs the ServiceManager.
Startable interface: It need to be housed separately to service-manager as otherwise it would lead to a circular dependency - as all services implement the Startable
interface and will depend on this module, but if it were housed inside service-manager which also needs to depend on individual services for the getXService()
functions, that leads to a circular dependency. May be we can house this under service -> common. ServiceManager
also implements the Startable
interface
New modules for all 3 services - AppLifecycle, VisibleScreenService, CurrentNetworkProvider can be housed under service -> app-lifecycle; service -> visible-screen; service -> network-provider.
NOTE - One major problem in the above approach is: All instrumentations need InstallationContext (new android-instrumentation module), that module needs service-manager module due to the 3rd argument in InstallationContext of ServiceManager. And service-manager needs dependence on all services due to the getXService()
functions. So, in turn anyway all instrumentations depend on all services. So, I think the solution here could be to define another AutoService interface for these services and then we can load and get services without actual class names in the ServiceManager. Similar style to AndroidInstrumentationLoader
- getInstrumentationByType() or getAllInstrumentation() methods.
Services as AutoService:
Service
- would be an AutoService interface. Currently there are two steps to all services - create and start. We can perhaps limit to just start. This interface would have these one/two method declarations. Similar to AndroidInstrumentation interface.ServiceLoader
and ServiceLoaderImpl
similar to current AndroidInstrumentation counterparts. ServiceManager
or ServiceLoader
in the InstallationContext
. individual instrumentations can retrieve the necessary services by calling ServiceLoader.get().getServiceByTpe("serviceName")
. When some user is utilizing just the individual instrumentation without core, they need to start the services in their classpath on their own Or Can we make it a part of the install method code - to start the associated services first, for that we need to ensure that start code is called only once (may be via a alreadyStarted boolean). Some other classes from core that are used in these services and any other improvements:
DefaultingActivityLifecycleCallbacks
class. May be instead of this, directly the super class can be implemented in slow rendering itself.This is an initial draft. Would be thinking through if it fits all our use cases and refining it more as needed. Do share your feedback on any of it. Also, missed proposal for periodic work service. Would add that as well.
A lot of what's mentioned here makes a lot of sense, although before making any final decisions on the matter, I'd like to point out some aspects of "services" to clarify how they are supposed to help us in general, hoping that we can come up with a solution that would still allow us to rely on them for future use cases.
Services are meant to be reused, for different reasons which can vary depending on the scope of each service, so I think we need to try and take a look at how we can easily reuse them for future cases.
For example, let's consider PeriodicWorkService
. This service is helpful to manage a queue of background work that can be repeated, in a way that reduces the amount of resources that might be needed should we needed to do this from different parts of our agent. For now, it's only used to run periodic exports from the disk by the disk buffering tool, however, most likely in the future we will also require to run periodic network requests to fetch the real time from an NTP server so that distributed tracing works well. If we can reuse PeriodicWorkService
for that purpose, not only we wouldn't have to come up with a custom mechanism each time we need to run these kinds of tasks in the background, but also we would be managing the agent's resources better as we can decide from a single place how many secondary threads we would like it to spawn at a time.
For the same use-case of the previous example, we might also need to use the Preferences
service, to store the current NTP time and avoid making too many requests to fetch it if it's not needed.
Based on the above, one of my concerns with some of the proposed changes would be that we might move some of the services code, or full classes, to the places where they are needed right now, making it difficult to reuse those tools for future cases.
Having the available services in a single place is useful to get an idea of what's available at any time, kind of like having a toolbox when working on a DIY project. I think this can still be accomplished when splitting services into micro-modules, so long as those modules are placed in a single dir. Failing to somehow provide visibility to existing services might cause issues in the future where people start reinventing the wheel in the best case and consuming resources unnecessarily in the worst.
If we decide to place each service in its own micro-module, we might eventually run into a scenario where we get too many modules to maintain, maybe it won't be too much trouble since we use gradle file conventions, though I think it's worth mentioning it just to make sure there's no problem with that, or if maybe we can alternatively group some services if needed to reduce the amount of modules. Also, if it's needed to create a new module just to add a new service I'd be concerned about people deciding to quickly write the code they need in the module they're working on to avoid having to set up a full module, a process that requires creating a set of dirs and files that maybe some people aren't familiarized with. If that ends up happening, then we would lose the benefits of keeping our tools in a single place.
I think there's a lot of room for improvement for services and I'm up to make changes to them as needed as long as they can still provide the benefits of sharing global tools easily. Probably one way to start could be by trying to see what future usages we might need for them and making sure that those will be easy to accomplish, and for those which we can't think of future usages for now, I think we should come up with a well-documented process of how to make them "globally accessible" if needed in the future.
Thanks @LikeTheSalad for your detailed review of the proposal and for explaining the points so clearly! :)
A few take-a-ways and some additional thoughts to carry our discussion forward:
Open questions:
Currently, services are part of the bigger core module. It would be beneficial to extract each of them in their own separate modules so they can be plugged-in as needed.
Example use case - Some services like PeriodicWorkService are needed if Disk Buffering is used (to export spans stored in disk to backend). If users/ vendors implement their own disk buffering mechanism, they do not need this service.
There are mainly 4 services (AppLifecycle, VisibleScreenService, CurrentNetworkProvider, PeriodicWorkService) and 2 classes (CacheStorage and Preferences). Objects are created from the 2 classes and the objects can be obtained from the service interface but they're not really services.
Following bullets cover what instrumentations rely on what services for more clarity, so we know not all instrumentations require all services