Hi folx!, I’ve been thinking about leveraging the resolution of dependencies from the Class level as a way to create a registry of schemas/contracts that allow me to help external callers of my module to resolve and utilize the schemas and get better information on what data is required to perform actions in my service. I can do this on the Container
I am currently using for IoC but it seems like it might be hard for others to know what dependencies are intended for external use and which ones are intended to support internal use cases. The notion of a Registry
creates some semantics and boundaries around the internal, vs external, resolvable dependencies. So I’ve been thinking of something like the following illustrates the high points of my idea:
# pull the contract and use to validate the payload matches the internal needs
class SomeplaceInRailsLand
def call(data)
# illustrating external resolution
contract = Documents::Registry.resolve("contracts.special_document_contract")
contract
.call(data)
.bind { |payload| Documents::SpecialDocument.archive(payload: payload) }
.or { |err| handle_error(err) }
end
end
module Documents
class Container
extend Dry::Container::Mixin
register "service" do
DocService.new
end
end
class Registry
extend Dry::Container::Mixin
register "contracts.special_document_contract" do
DocumentContract.new
end
end
Import = Dry::AutoInject(Container)
class DocumentPayload
extend Dry::Initializer
end
class DocumentContract < Dry::Validation::Contract
include Dry::Monads[:result]
def call(**args)
res = super
res.success? ? Success(DocumentPayload.new(res.to_h)) : Failure(res)
end
end
class Archive
# illustrating internal resolution
include Import["service"]
include Dry::Monads[:result]
def call(payload)
Success(service.perform)
end
end
module SpecialDocument
module_function
# @param payload [DocumentPayload]
def archive(payload:)
# use the payload data to archive the document
Archive.call(payload)
end
end
end
I think this might be nice as it decouples the contract used (I could even manage to register multiple versions or make forward-compatible adjustments to the schema without changing external callers and make type annotations (which remain internal to the service boundary).
I’m curious if 1) anyone else is using more than one container in this fashion and 2) for those with more experience, anything that stands out as an antipattern?
Thanks in advance!