* Service.build takes the literal YAML config hash for the service and a
reference to the Configurator that's doing the building.
* Services that compose additional services can use the Configurator to
look them up and build them by name. See MirrorService for an example.
References #23
* Move service configuration from the Engine to Service
* Delegate configuration mechanics to internal Service::Configurator
* Delegate service building to the concrete Service classes, allowing
them to configure composed services.
* Implement for the Mirror service.
Pass separate primary service and list of mirrors rather than treating
the first of the services list as the primary. Nice fit for keyword args,
and something we've long wanted in the equivalent Basecamp file repository.
Upload returns the results of the underlying service uploads rather than
the io.rewind result. Rewind before uploading rather than afterward, and
demonstrate that behavior with a test.
Test that more than one mirror works.
The mirror service exists for the purpose of migration, where all blobs exist in the primary subservice and a subset of blobs exist in the secondary subservice. Since the primary subservice is the source of truth until a migration is completed, operations like existence checks need not be performed against the secondary subservices.