A Cache
maintains an internal state with a mapping from requests to Ref
s
that will contain the result of those requests when they are executed.
A Cache
maintains an internal state with a mapping from requests to Ref
s
that will contain the result of those requests when they are executed. This
is used internally by the library to provide deduplication and caching of
requests.
A CompletedRequestMap
is a universally quantified mapping from requests
of type Request[E, A]
to results of type Either[E, A[
for all types E
and A
.
A CompletedRequestMap
is a universally quantified mapping from requests
of type Request[E, A]
to results of type Either[E, A[
for all types E
and A
. The guarantee is that for any request of type Request[E, A]
, if
there is a corresponding value in the map, that value is of type
Either[E, A]
. This is used by the library to support data sources that
return different result types for different requests while guaranteeing that
results will be of the type requested.
A DataSource[R, A]
requires an environment R
and is capable of executing
requests of type A
.
A DataSource[R, A]
requires an environment R
and is capable of executing
requests of type A
.
Data sources must implement the method runAll
which takes a collection of
requests and returns an effect with a CompletedRequestMap
containing a
mapping from requests to results. The type of the collection of requests is
a Chunk[Chunk[A]]
. The outer Chunk
represents batches of requests that
must be performed sequentially. The inner Chunk
represents a batch of
requests that can be performed in parallel. This allows data sources to
introspect on all the requests being executed and optimize the query.
Data sources will typically be parameterized on a subtype of Request[A]
,
though that is not strictly necessarily as long as the data source can map
the request type to a Request[A]
. Data sources can then pattern match on
the collection of requests to determine the information requested, execute
the query, and place the results into the CompletedRequestsMap
using
CompletedRequestMap.empty and CompletedRequestMap.insert. Data
sources must provide results for all requests received. Failure to do so
will cause a query to die with a QueryFailure
when run.
A DataSourceAspect
is an aspect that can be weaved into queries.
A DataSourceAspect
is an aspect that can be weaved into queries. You can
think of an aspect as a polymorphic function, capable of transforming one
data source into another, possibly enlarging the environment type.
A Described[A]
is a value of type A
along with a string description of
that value.
A Described[A]
is a value of type A
along with a string description of
that value. The description may be used to generate a hash associated with
the value, so values that are equal should have the same description and
values that are not equal should have different descriptions.
QueryFailure
keeps track of details relevant to query failures.
A Request[E, A]
is a request from a data source for a value of type A
that may fail with an E
.
A Request[E, A]
is a request from a data source for a value of type A
that may fail with an E
.
sealed trait UserRequest[+A] extends Request[Nothing, A] case object GetAllIds extends UserRequest[List[Int]] final case class GetNameById(id: Int) extends UserRequest[String]
A ZQuery[R, E, A]
is a purely functional description of an effectual query
that may contain requests from one or more data sources, requires an
environment R
, and may fail with an E
or succeed with an A
.
A ZQuery[R, E, A]
is a purely functional description of an effectual query
that may contain requests from one or more data sources, requires an
environment R
, and may fail with an E
or succeed with an A
.
Requests that can be performed in parallel, as expressed by zipWithPar
and
combinators derived from it, will automatically be batched. Requests that
must be performed sequentially, as expressed by zipWith
and combinators
derived from it, will automatically be pipelined. This allows for aggressive
data source specific optimizations. Requests can also be deduplicated and
cached.
This allows for writing queries in a high level, compositional style, with confidence that they will automatically be optimized. For example, consider the following query from a user service.
val getAllUserIds: ZQuery[Any, Nothing, List[Int]] = ??? def getUserNameById(id: Int): ZQuery[Any, Nothing, String] = ??? for { userIds <- getAllUserIds userNames <- ZQuery.foreachPar(userIds)(getUserNameById) } yield userNames
This would normally require N + 1 queries, one for getAllUserIds
and one
for each call to getUserNameById
. In contrast, ZQuery
will automatically
optimize this to two queries, one for userIds
and one for userNames
,
assuming an implementation of the user service that supports batching.
Based on "There is no Fork: an Abstraction for Efficient, Concurrent, and Concise Data Access" by Simon Marlow, Louis Brandy, Jonathan Coens, and Jon Purdy. http://simonmar.github.io/bib/papers/haxl-icfp14.pdf