-
Notifications
You must be signed in to change notification settings - Fork 76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement wireResource #173
Comments
I imagined sth along the lines: wireResource[F, T]: Resource[F, T] I think it would make sense to make it recursively search for values, just as class DatabaseAccess()
class SecurityFilter()
class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter)
class UserStatusReader(userFinder: UserFinder)
trait UserModule[F[_]] {
import com.softwaremill.macwire._
// some code to initialize & cleanup a pool of database connections
lazy val theDatabaseAccess: Resource[F, DatabaseAccess] = ???
lazy val theUserStatusReader: Resource[F, UserStatusReader] = wireResource[UserStatusReader]
} would generate sth along the lines of: trait UserModule[F[_]] {
import com.softwaremill.macwire._
// some code to initialize & cleanup a pool of database connections
lazy val theDatabaseAccess: Resource[F, DatabaseAccess] = ???
lazy val theUserStatusReader: Resource[F, UserStatusReader] = theDatabaseAccess.map { da =>
new UserFinder(da, new SecurityFilter()))
}
} Multiple resources would be flatmapped. Plus we would need to figure out the right order of flatmapping, so that all necessary values are in scope. Wdyt? |
Looks promising for me, but I would rather name the function that @adamw proposed When it comes to the |
@mbore yeah I was thinking about it, I agree that we should start with cats-effect. ZIO has their own dependency management system (through environment), so I think we'll have a tougher timer convincing them that our way is better ;) We can later try to generalise through a As for concrete implementation, it will have to diverge a bit from the current design:
We could also use this graph to provide better error reporting - when a value is not found, we can print the path to that dependency, from the wiring root. |
I discussed this feature with @adamw and we would like to start with a slightly simplified version of this feature. We would like to make class DatabaseAccess()
class EsAccess()
class SecurityFilter()
class UserFinder(databaseAccess: DatabaseAccess, esAccess: EsAccess, securityFilter: SecurityFilter)
class UserStatusReader(userFinder: UserFinder)
trait UserModule[F[_]] {
import com.softwaremill.macwire._
lazy val theDatabaseAccess: Resource[F, DatabaseAccess] = ???
lazy val theEsAccess: Resource[F, EsAccess] = ???
lazy val theUserStatusReader: Resource[F, UserStatusReader] = wireResource[UserStatusReader](theDatabaseAccess, theEsAccess)
} and we should generate smth like trait UserModule[F[_]] {
import com.softwaremill.macwire._
lazy val theDatabaseAccess: Resource[F, DatabaseAccess] = ???
lazy val theEsAccess: Resource[F, EsAccess] = ???
lazy val theUserStatusReader: Resource[F, UserStatusReader] = theDatabaseAccess.flatMap(da => theEsAccess.map { ea =>
new UserStatusReader(new UserFinder(da, ea, new SecurityFilter()))
}
} By "less magical" solution, I mean that we want to eliminate the following problems:
In my opinion we should chain ( In the next step we would like to take also as parameters functions which return class DatabaseConfig private ()
class DatabaseAccess()
object DatabaseAccess {
def apply[F[_]](cfg: DatabaseConfig): Resource[F, DatabaseAccess] = ???
}
class SecurityFilter()
class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter)
class UserStatusReader(userFinder: UserFinder)
trait UserModule[F[_]] {
import com.softwaremill.macwire._
lazy val databaseConfig: DatabaseConfig = ???
lazy val theUserStatusReader: Resource[F, UserStatusReader] = wireResource[UserStatusReader](databaseConfig, DatabaseAccess.apply)
}
and we should generate trait UserModule[F[_]] {
import com.softwaremill.macwire._
lazy val databaseConfig: DatabaseConfig = ???
lazy val theUserStatusReader: Resource[F, UserStatusReader] = DatabaseAccess.apply(databaseConfig).map(da => new UserStatusReader(new UserFinder(da, new SecurityFilter())))
But in this case it would not be so easy to set up the order of generated resources |
@mbore thanks for the summary :) I think the ordering should be left to the library - that is, this should be the library's problem. It's trivial when resources don't have dependencies, but once they do have them, it's not necessary to put the burden of properly ordering the definitions on the user. I'd go even a step further - making it necessary to list all dependencies, that shouldn't be auto-generated, as explicit parameters of |
Regarding "explicite list of all dependencies" I was thinking about it some time and I think that it's a good idea to start with this approach (because it's simpler :)) and later on we may consider some kind of recursive scopes. I think that implementing it this way would be welcomed from the users point of view, because it would be easier to keep the backward compatibility. Edited my previous comment to stress that we will require all "impure" dependencies. |
Which part of the design addresses the staged sequencing of bootstrap? In my mind this feature should allow a less chaotic bootstrap (example waiting for cluster forum to bootstrap or similar, long-running procedures). |
@schrepfler it probably doesn't, as we've focused on other aspects :) And I think I don't fully understand your requirements. What should separate the bootstrap stages? Would you like to specify some specific ordering in which |
So, this came as an idea when we were discussing the bootstrap of Lagom (and thus Guice injection and Akka Cluster and Kafka consumers and producers). In that case your entire system is maxing out at 100% CPU and can't understand what's going all. We're hypothesising that if we'd bootstrap everything in a controlled and staged approach we'd probably have a much smoother start and the system would get stable sooner. |
@schrepfler Ah I see :) so how would you like the control to look - is it the ordering of when the resources are created? Or the general order in which dependency instances are created? Or maybe you're looking to parametrising parallelism of resource creation as well? I think some example fake-service classes and the target code which you'd like to generate would help in design. |
I started working on this feature and my first attempt (which you can find in #175 ) is a really simple one. Basically I compose provided resources and then run simple
into
Now I'm going to replace @schrepfler Your idea seems to be really interesting, but we definitely need more details to design it :) |
@mbore Looks like a great first step - esp if it works :) I'd like to go a step further with |
Implement some sort of deferred wiring which can express some sort of dependency delayed in time. This would allow more precise loading of components when they're ready to be loaded thus not kicking all dependency injection at bootstrap which often kicks off a lot of work for nothing.
Unsure what's the best approach in terms of expressing the dependency, chaining effect types? How to deal with initialization failures... maybe lean on concrete effect types error handling mechanism.
The text was updated successfully, but these errors were encountered: