-
Notifications
You must be signed in to change notification settings - Fork 20
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
What do we do when a project wants a query that's specialized? #208
Comments
Possible query extension patterns: Build a new
|
What I want to avoid is building out a feature that's basically just slower funnier-looking monkey-patching. Part of me likes option 1, but I can't put a finger on what benefit it gives really - other than some level of improved testability, I guess? But the sub-class option offers that too. |
Subclassing seems like the most straightforward way to do this to me — and would make it easier for overriding the default query implementations if you wanted to for some reason. |
Subclassing seems fine, but its a shame you have to sub class two things and keep an eye on them (both the adapter and query service) |
I'm thinking it would be really beneficial to not extend the interface of the query service with application-specific methods. An alternative here is to allow a "special queries" object/factory to be registered with the query service -- I'll call this The advantage, in terms of maintenance, is that it is very explicit which application code uses the Valkyrie query service and which uses the "homegrown" extensions. If the "registration" provides a way for So in pseudo-code: # somewhere in your app's lib/
class AppQueryFactory
def instance(query_service)
CrazyAppQueries.new(query_service)
end
end
class CrazyAppQueries
attr_reader :query_service
def initialize(query_service)
@query_service = query_service
end
def find_cool_things
ids = query_service.some_standard_query.map {|item| item.id }
query_service.connection.raw_query "some crazy native query (?)", ids
end
def find_something_from_totally_different_backend
other_backend.query "something totally different"
end
end # some config
Valkyrie::QueryService.register_app_extensions(AppQueryFactory.new) # some use case / service object
class AwesomeUseCase
def do_cool_stuff
do_something_awesome_with query_service.app_queries.find_cool_things
end
end |
@botimer I think that's the same thing as option #1 above, especially since Valkyrie::QueryService.register_app_extensions(AppQueryFactory.new) would have to be changed to be specific to a single adapter (unlikely extensions will be adapter agnostic.) It's nice that it doesn't allow overriding queries, but again - it's a Valkyrie specific solution for something ruby can just solve, and I want to make sure it's worth it. I really do prefer it, but it's more of a gut feeling that I can't really pin down with words, which seems like a problem. |
@tpendragon Hmmm.. I guess I read option 1 as very different... as in the semantics/syntax of the underlying tech poking through a generic interface. Something like I saw my example as closer to 2a/b. I still do see it as different, though. The main issue in my mind is: is it clear from the calling code context whether something is core or an extension? If it is not (as by grep/static analysis), it takes serious forensics to track down the customizations. That feels like a creepy thing to do in a project geared around clearer architecture and a strictly defined API you can count on. Also, I agree that they are almost certain to be adapter-specific (a bug in the pseudocode). On the other hand, if you were, implementing a new query for all of the stock backends, you could have the component consult lower-level config, or just require that the user add a bit of adapter-specific config to match their infrastructure. I think that'd be an edge case -- someone willing to do that much work would probably be in touch and it would be worth considering adding to the core interface (option 3, possibly in a "contrib/javax/labs/probationary" area). In general, I think it is pretty expected that someone doing customization like this will be doing it for their infrastructure -- they have already chosen, so putting that choice into some config is not a problem. |
@botimer I think the big difference between my #1 and your suggestion is that yours sits behind another background collaborating object - which I don't think is a bad idea (although, it's going to violate Demeter all over the place.) Here's an example of what the implementation would look like: https://github.com/samvera-labs/valkyrie/compare/extendable_query_services |
What I like most about this pattern is we can pass these query customizations around between us and not have to deal with any inheritance hurdles. |
Yeah, I don't mind the Demeter violations here -- the return on clarity and maintenance effort seems worth it. The "my base query service has different behavior than yours" is almost certain to cause a lot of confusion across apps and time, whereas clearly having a cave means we can say "there be dragons". I think I prefer that. An implementation-level thing, the method_missing approach kinda-sorta feels good, but it also feels a little weird. I suppose there is a temptation to plan for a union of custom additions -- not sure whether to push back on that or not. It's really the same concern, one level deeper. Do you locate an object to call a known method on or send a message to a broker who passes it on to a collaborator? As long as there aren't runtime extensions to the base API we've been trying to define clearly, I think these are sort of 50-50 trades "in the cave". |
The scenario:
I have a special query which is necessary for my repository to work, but Valkyrie doesn't currently provide it. IE, "Find everything which is in the collection Y" or "Everything with the checksum Z"
Possible solutions:
a. Maybe this is just a recommendation to write their own query service if they need new queries, and know if they switch they'll have to solve that same interface?
b. Maybe this is a way to register an alternative query service in an adapter, to fall back to if the base adapter doesn't support it?
c. For either A or B we'd have to formalize the ResourceFactory pattern again.
find_by_checksum
andfind_by_collection_uri
) to Valkyrie as it comes up.The text was updated successfully, but these errors were encountered: