-
Notifications
You must be signed in to change notification settings - Fork 40
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
Tight coupling of "HTTP servers, as interface" to "Implementations" is painful #5247
Comments
We already have a small example of this pattern in installinator-artifactd, for reference. edit: here's the trait |
I'm not totally sure what you're proposing. To generate the OpenAPI spec, you'd need the entire dropshot server in |
I'm admittedly being loose with what I'm proposing to avoid being too prescriptive -- I wanted the issue to mostly describe the problem space, not the solution space, beyond "decoupling would be nice". Personally I'd really like the following: Dropshot currently wraps an #[dropshot_server]
pub trait SledAgentService {
#[endpoint { ... }]
async fn xyz(ctx: RequestContext<Self>, query_params, path_params);
} This could exist in a impl SledAgentService for SledAgent {
async fn xyz(ctx: RequestContext<Self>, query_params, path_params) {
...
}
} Granted, it's also possible to do this separation "below the dropshot layer" by putting a But this would:
|
I'm pretty strongly in favor of this decoupling regardless of implementation. I think the problems Sean has run into are real, and it is often frustrating to me to get these OpenAPI specs to generate even with just my own local changes. I don't think it's too much boilerplate either. We have much worse elsewhere. Some of it rhymes with weasel. |
So, we'd define the interface first as a trait. Would we need some way to emit the OpenAPI document from that trait (indirectly, I'd expect)? An alternative (more traditional) approach would be to define the interface e.g. in OpenAPI directly or in something like TypeSpec and then generate a trait (or stubs or both) from that. Presumably we'd imagine this as an alternative mechanism to the current approach in Dropshot rather than as a replacement? |
I don't think anyone wants to change the autogeneration of specs from dropshot. That's the best part of dropshot. My complaint was mainly about when I create my own circular dependency, not how the spec generation happens. Although I know @ahl reasonably figured I was just being an ahole 😄 |
To be clear: I think it's reasonable to consider an alternate, additional interface. |
Yes. I think that should be relatively easy.
I don't want to write OpenAPI directly, but TypeSpec looks quite interesting! My worry is the loss of fidelity inherent in generating Rust from OpenAPI -- we've run into issues with this within clients which have required lots of
Definitely an alternative, yeah. At the very least we'll want to convert one service over at a time. In the longer term, it would be interesting to see if the alternative is better enough that it should become the only way to write Dropshot servers. |
I spoke with @smklein about this a bit. These seem like good goals for an alternate interface to Dropshot. I'm inclined to prioritize this behind two other dropshot-related concepts I've been considering: multi-version support and multi-API per server support. But it's helpful to consider this alongside these other bodies of work. I've assigned this to myself, but if someone else comes along with cycles and interest by all means feel free to take it over--I'm not trying to exclude others from investigating. |
As of #6486, this is now done. |
This may be an issue related to the dropshot repo too, as Omicron isn't the sole user of this pattern.
This issue describes a process which is documented in our README, but which is still painful: https://github.com/oxidecomputer/omicron?tab=readme-ov-file#generated-service-clients-and-updating
Background
http_entrypoints.rs
throughout the codebase) which utilize dropshot macros and ingest types to define the specification.When is this a problem
Mostly: When things don't compile, it can be exceptionally difficult to "regenerate the interface we want".
In the cleanest scenario, you'd make changes to your implementation AND interface together, then run the "openapi spec generation tests", then apply those changes to all clients which exist in the codebase. However, there are scenarios where this breaks:
todo!()
and commented-out code to get to a compiling state, then undoing all those changes immediately afterwards.A better world?
It would be really nice to decouple the implementation of these interfaces from the interface declarations themselves.
For example, if the Sled Agent API was not actually part of the sled-agent crate, but rather, a separate "sled-agent-interface" crate, we could do the following:
EXPECTORATE=overwrite cargo nt -p sled-agent-interface
could re-generate the client bindings, regardless of whether or not "the rest of sled agent compiles"sled-agent
crate could depend on thesled-agent-interface
crate, and use it as a normal part of exposing an HTTP serversled-agent
'shttp_entrypoints.rs
-- could actually rely on the same interface.The main advantage here would be "when you want to generate the new interface, do that first, before necessarily touching the implementation" and that would work. This should remove the need for "commented-out" code to get things to compile, since it would no longer be necessary to make the rest of the crate arbitrarily compile before generating the new interface.
The text was updated successfully, but these errors were encountered: