-
Notifications
You must be signed in to change notification settings - Fork 611
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
Restructure Pyroscope API into groups that can be enabled / disabled #869
Comments
I can't say I fully support the idea of modular API in pyroscope server. Regarding API groups: although, I see some pros for us as maintainers of the API, I also see that for must API consumers this will be merely inconvenient in use (and often won't meet expectations). BTW, about the URLs: for groups it should be Unrelated to API groups: I also think that disabling parts of the API should be discouraged. At least, endpoints should not return 404 in this case as it interferes with the handler logic. Existing Controller should be definitely refactored. The way I picture it is described in the |
Thanks for your feedback @kolesnikovae !
Can you elaborate on why this is going to be inconvenient and won't meet expectations? I understand that for an API user that would like to use all our API groups, it'd be easier to just have a single version for all our API, as they could just use a single client. Not sure if that's the inconvenience you are considering or there's something else. If this is the case:
Can you elaborate on what problems disabling endpoints causes? I can see some benefits to disabling endpoints (e.g. on a pull-based deployment not disabling the ingestion endpoint is inconvenient, same for exposing the adhoc API while it's disabled, etc.)
I have to admit that I left the details of the implementation of the controller out of the scope of this issue on purpose, as that's another huge topic to discuss by itself :). I agree that many (most) implementations work in this way. How we want to implement our API depends on what design we want to follow. If we want to have a monolithic design, that's a valid approach, but I do see value to have independent and unrelated components decoupled (e.g. having single |
Let's consider topics separately, as they are really independent. 1. VersioningWe may need API versioning. I agree with the authors of the API versioning article and Roy Fielding (creator of REST):
More specifically, I think that at this point we only need versioning of the profiling data formats, not the API endpoints. I'd add /v1 version on Pyroscope v1.0.0 release. 2. API GroupsBy API Group I mean a set of handlers with a common URL prefix including the version. I think that this is a premature measure that will complicate use of the pyroscope server API without any good for us and consumers (at this point at least).
Maybe we have different visions of the API groups and I'm wrong, but I don't think API groups are helpful even in big projects. 3. Disabling endpoints
We should disable features, not endpoints, I believe. API server should return a proper response with the corresponding code (e.g. 403) and a message, explaining that the feature is disabled. Otherwise, users may find it obscure: for example, as API consumer I want to know why We don't need API groups to manage this. 4. Monolithic design
I fail to see how it's related. If this is matter of packages structure, then I have to say that using single package for multiple domains may not be the best approach for a big project, but in our case (and most cases) it works better: you don't need to import dozens of packages with colliding names and creating aliases for them, spend time on thinking whether this thing belongs to this domain or that, make some types and functions public only because of this, and so on.
I do believe that keeping all API handlers in one package (maybe with sub-packages, if needed) is more explicit, clear, and safe (read: better) than scattering the API logic among the repository. You can always register a subset of routes, if needed. |
Great points, thanks for sharing! I think we agree on some of those points and disagree on some others, let's explore them a bit more :) 1. Versioning
I'm fine with that. I agree that we don't need v1 from the very beginning, but I don't think it makes any harm either (especially if we end up with more than one version). But note that we already have some API endpoints with 2. API GroupsI think the points you raise refer to the same granularity problem (which could be summarized with something like: if we have too many groups and they are coupled then it's worse for us and our users). I agree with this, and I don't think we should create groups for things that are related, they should be used for decoupled components. I gave some examples of what these components could be, let me go over them again and use them as an example. Let's three components/groups: adhoc, user management and profile data ingestion:
3. Disabling endpoints
I'm fine with this. We can call them features instead of groups if that solves the granularity problem (that is, I'm not sure why API groups would have boundary problems but features would not, so maybe we are thinking about different granularity levels). 4. Monolithic design
It's related because a decoupled API design goes along with a decoupled API implementation more naturally, the same way a coupled API design goes along better with a coupled API implementation. Of course, there's no need to have them both together, but there's otherwise a mismatch that requires some extra work.
Maybe we have different ideas of what having decoupled components means, because it certainly doesn't require importing dozens of packages, or having a hard time deciding what parts belong to which component (e.g. the adhoc server is decoupled from the main controller and suffers from none of those problems). It certainly requires some work on the API (interfaces) of the components to isolate them (which is not mandatory, and can be built over time), which is a good thing, in my opinion. It also reduces significantly the cognitive load when working on some particular component, due to its isolation.
Personally, I don't see much value in having it all in a single package, but I don't have a problem with that either, if we decide against API groups (I don't think subrouters or subpackages would make much of a difference in this approach, but I'm fine with those). |
I wanted also to draw some conclusions but didn't want to mix them with discussion, so I'll put them in a separate comment here. I'd say that we have different views on the monolithic vs modular approach (both the API and implementation), and I think the rest of the points come from this difference. The approach is a trade-off, and Pyroscope is small enough to make it manageable in either case, so I'm fine with any of them, it's more important to be consistent with it, in my opinion. Based on the discussion so far, I think it makes sense to go with a more monolithic approach and leave decoupling aside, for now, maybe revisit it if things get less manageable. It'd be interesting to make these conclusions and the preferred approach more explicit, maybe in the architecture docs, so we are aligned here. |
My point is not that API groups are harmful, but is that this is a premature measure. We're shouldn't make very bold assumptions in the very beginning. Okay, so I think we already can make some decisions: 1. API VersioningWe use URL versioning, and put
Existing routes are still handled; to be removed in v1.0.0 release. 2. API GroupsWe don't use API groups. 3. Disabling API handlersAPI handlers of disabled features return 403 with the corresponding error message, if the feature is disabled. For consistency, there should be a middleware that handles this based on the enabled features. 4. Packages layoutI find this topic pretty opinionated. I should say there are many options, I picked one that I like more based on my preferences, vision of the pyroscope future, and the current state. Honestly, I can't agree that the But I see your point that organizing packages by component instead of by layer may be beneficial (especially if we have very few components that can be easily isolated). If you feel strong about it, let's refactor. What I don't want to is to register http handlers in the component packages - the consumer may need only a subset of the HTTP API. |
Some design decisions are hard to change once they are made and I think this is one of those and that's why I opened the whole discussion, to begin with :)
Yay 🎊
👍
👍
👍
Note that this means that we'll potentially have the business logic regarding what features are enabled/disabled in two different points (instead of one):
But this goes along the chosen design, so 👍
True that, the whole issue is mostly opinions on what's the best approach, in the API design, package design, etc. created because we already have 3 different API designs, which is the root problem. So I also picked the approach I consider best based on what we have. If we don't agree there, that's fine, we can keep a layered approach instead of a modular one. Note that we need to refactor things in any case, as we have 3 different approaches already: first a controller with no layer separation, then a modular API for adhoc, finally a layered API for users. We should converge into one in any case. If that's the layered approach, we should refactor the original and adhoc APIs. So, based on this I think we should:
|
I was looking into different API endpoints we have and just wanted to document this here. I generated all routes by adding this function to
And then I tried to structure them:
|
Closing this as it is outdated and APIs have gone through quite a bit of changes since. If the need for restructuring arises again, we can revisit this. |
Background
Pyroscope API has been growing over time in an unstructured manner, which will make it harder to maintain over time. Some of the problems it currently shows:
/ingestion
endpoint is part of the API but doesn't have an/api
prefix in the URI, as other API endpoints do.Proposed solution
Following the k8s terminology, I propose to use API groups instead, and provide a consistent mechanism to enable/disable these groups:
The adhoc API shows how this could be achieved (API documentation missing).
The text was updated successfully, but these errors were encountered: