-
Notifications
You must be signed in to change notification settings - Fork 221
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
flatten models packages in Go to avoid unsupported circular references #2834
Comments
Thanks for reaching out on the topic. The go refiner already has a step that removes properties in models that depends on sub-packages to avoid circular dependencies. With the level of details you've provided, it sounds like your scenario should already be covered as working? |
My problem is with properties from sub-packages, referencing other sub-packages. I've created a very simple spec with I think the only viable route would be to place all model classes in a single package. This would however give rise to some other issues, most notably duplicate type names. Perhaps, a solution would be to rename the types to include the sub-package name in the type name. I believe this is a common practice for other generators. I can start working on a PR if you agree with this approach. |
No I don't think flattening namespaces would be a viable solution, it'd result in conflicts (we have the case in Microsoft Graph). Indeed the step "looks down" but doesn't look at siblings, or children of siblings. What I'm a bit worried about with a "naïve approach" of projecting properties against every property is that:
The better thing to do would be to implement a cycle detection algorithm which probably represents a significant undertaking at this point. |
Naive flattening would indeed result in conflicts, but a different strategy would be to rename the classes during the flattening. For example, if I take some names from the Graph API:
This should prevent most conflicts. Any remaining conflicts could then be solved using a simple postfix with an increasing number (this will probably be very rare). Removing properties that cause cycles in the dependency-graph is possible, but as you already said, it will be hard to do in a stable way, and even then, you are removing properties defined for a reason in the API. In my simple case with a.a pointing to b.b and vice versa, how would you decide which property to remove and how would the developer using the SDK know this happened and still set or read these properties? In our API, we've got quite a few of these references to sibling packages and many of them are required properties. We often create a superclass to be a reference to another entity. So you have |
In your example, what do you expect to happen if microsoft.graph already contains SecurityGroup? |
That's the big problem you always face when you are changing names, but I think a simple increasing number at the end should do the trick. So one of the types will be named |
Thanks for developing the thought to the end. The other major issue with moving the class is that it represents two breaking changes:
Both of those go against our support policy and would require a major bump in the first case, and wouldn't have a solution in the second case. Trimming properties, although not ideal, makes for a more stable generation result over time. What do you think? |
My intention was to always entirely flatten the codespace of the models module. This would be stable with any regeneration of the client and be very predictable with changes to the OpenAPI spec. Golang being a stable language, this does indeed require a major release, which is unfortunate. Trimming properties does not generate a stable API either. Adding a property somewhere, might cause some other property to be removed. The generator has no way of knowing which property existed in the old API. In my simple I'm not an experienced Golang developer, but my search so far seems to suggest that putting your entire model in one package is the preferred way of doing it in Golang. I've found several large SDKs which use this approach. |
The current implementation is deterministic in the sense that it removes properties that "point down". But yes, I agree that coming up with something deterministic for a broader loops context over time will be challenging at best. Here are two discussions I've been part of which are relevant to this issue: The first one is at the API council at Microsoft while working on the namespacing API pattern guideline. Long story short we ended up recommending that type names across namespaces DO NOT need to be unique, because it'd defeat the purpose of namespacing. And we also recommended for client applications TO NOT flatten namespacing (although that's not part of the published guidance for some reason). While working on Go generation in the early days, we faced performance issues with the Microsoft Graph Go SDK (generated from Kiota). And I reached out to people working on the Go compiler itself. Their feedback was along the lines of "don't make main packages (root + sub packages) that are so large", "subpackaging is supported, don't nest too deep", "break into multiple main packages if you can". Obviously, that's hard to conciliate with most REST APIs that usually make use of namespacing, have a large number of inter-related models spanning across packages. I understand the Go community made early design choices to keep things as simple as possible. But my opinion is that it's reached a point where some of those choices are non-practical in a large number of scenarios. (the other example I have in mind being how generics work...). API producers have come to expect the client application will have a number of capabilities based on very basic language capabilities that are available today in the vast majority of programing languages out there. Now, that additional context while useful doesn't solve your problem. But I don't think a solution where we flatten namespaces would work at this point. If we can come up with a deterministic way to detect loops and clip them deterministically it'd be a huge win. And it'd also go a long way to unblocking our ruby work. |
I don't think we want this issue to be closed. This is a real issue with the current Go generation, that cannot be fixed before 2.0 and will require an explicit decision between either of these two options:
|
That bot is driving by tags. And we use the tags to "force the conversation to a decision or to be closed" as you rightfully pointed out. |
Yes, I agree. I think flattening is the go-way of doing it, but this can't be done before v2. Trimming is a lot of work and hard to impossible to get deterministic. Let's park this for v2. |
For larger APIs, it is nice to put different parts of the model in different namespaces. For example, in our API, we've got over 40 types related to authentication and about 30 related to groups of users. In Java, we've put these classes in different subpackages, and I think this works well for other languages, such as C# as well. However, these types do have relations pointing to types in other subpackages, such as accounts being member of a group and the groups an account is member of. These dependencies often form cycles. This currently breaks code generation for Go, as Go does not allow circular dependencies between modules.
I'm not sure what the best way to fix this would be, but I guess the best solution would be to flatten the models modules in Go and generate one large module. I think cross-type relations are quite common in APIs and requiring the provider of the API to not use relations over namespaces does not seem viable to me. IMHO this should be implemented in the
GoRefiner
.The text was updated successfully, but these errors were encountered: