Skip to content
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

Updated specifications for the Graph JS v4 Service Library #1104

Open
wants to merge 8 commits into
base: 4.0.0
Choose a base branch
from

Conversation

sebastienlevert
Copy link
Contributor

@sebastienlevert sebastienlevert commented Dec 15, 2022

This is a first stab at providing more context around the v4 service library and the usage of the selective imports capability. I'd love to learn what you feel would be missing, any questions that this doc could help answer and comments on the overall document.

Copy link
Member

@baywet baywet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this together.

design/service-library.md Outdated Show resolved Hide resolved
design/service-library.md Outdated Show resolved Hide resolved
> This example succeeds because `me` is at the root of the Graph Client

```typescript
const me = await graphClient.me.get();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should make a different between first level path segments and the rest. This introduces a inconsistency of experience.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're losing a lot of discoverability if the bare API Client has no references to anything. We're not talking about the self-serve option here, we're expecting the "full" API available. Thoughts @darrelmiller @gavinbarron @nikithauc

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're in the realm of trade-offs here. I agree that import hurts discoverability a lot, the benefit is that by being explicit which children of the root node you want to pull in then the produced app bundle becomes much smaller as it's not including root nodes your app doesn't want.

My instinct here is to try the more discoverable approach in the first instance and evaluate the produced bundle size to see if we need to optimize for size over discoverability.

```typescript
import "@microsoft/microsoft-graph-client/users/item";
const directReports = await graphClient.usersById("ab677c1b-50c6-4013-aa9f-792ed42c59c8").directReports.get();
const extensions = await graphClient.usersById("ab677c1b-50c6-4013-aa9f-792ed42c59c8").extensions.get();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/item/extensions hasn't been imported, this should fail too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extensions is a child resource of /users/item, so I expect extensions to be available. Am I seeing this wrong? I'm not asking for a specific extension, but the collection.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the same understanding as Sébastien here. Importing a node of the tree makes all of its direct children available.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to understand the expected behaviour. If we're expecting the first level nodes to be automatically imported for discoverability and child nodes for any nodes to be imported automatically as long as the parent is imported, we're effectively pulling the full tree and not doing any selective imports anymore.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding was that we'd be importing a node and that would make it's children, and no deeper in the tree available, e.g. importing /users makes /users/{id} available, but not /users/{id}/directReports as this would require /users/item to be imported, which would also make /users/{id}/extensions available.

Similarly, the root node makes all first level paths available, and in that case that node is already imported implicitly.

const directReports = await graphClient.usersById("ab677c1b-50c6-4013-aa9f-792ed42c59c8").directReports.get();
const extensions = await graphClient.usersById("ab677c1b-50c6-4013-aa9f-792ed42c59c8").extensions.get();
const extensions = await graphClient.usersById("ab677c1b-50c6-4013-aa9f-792ed42c59c8").extensionsById("85147e97-ed89-4799-b5b8-8d35a9204604").get();
const joinedTeams = await graphClient.me.joinedTeams.get();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for joined teams

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If /users/item is already imported, /me/joinedTeams should be available. Or should we specifically import for /me? I think of /me to be a shortcut for /users/{user-id}.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly Kiota has no way of telling that /me and /users/id are aliases, so there needs to be separate imports here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the repetitive nodes aspects is something we've been talking about while investigating the go build performance issues.
Effectively the OpenAPI spec has no way of describing "this node is a clone of that other node". We could get around that by using an extension.
Now the next problem becomes, does the conversion process make the information available? Semantically speaking, because of OData notations, two nodes can be really similar but not identical (ignoring for description and path). For example, a node could support a PUT/PATCH operation in one context but not in the other, or have a OData method attached in only one context.

Assuming that the conversion library solves for that, we now have a deterministic behaviour issue.
E.g. I convert my metadata and generate SDKs in September, at this time /me and everything under it is identical to /users/id and everything under it.
The SDK generates a single set of request builders, inline requests and responses types.
Then in October somebody adds an annotation on /me but not on /users/id. The conversion process judges that those nodes are now different, produces the description accordingly, and Kiota generates two sets of types. Effectively breaking half the applications that had references "from the wrong path".
We could major bump every-time that happens, but with a service the size of Microsoft Graph and the highly repetitive nature of OData I anticipate this is going to be extremely painful for us and for our customers.

So the conclusion of that was not to deduplicate nodes automatically but make conscious and careful decisions when we see opportunities like that using kiota's filtering capability and a little bit of handcrafted code in the service lib. A good example of that is /me/ and /users/id. They overlap 100% today, are huge, and even if they started drifting in the future, we'd keep the deduplication going and tell customers impacted by the edge case that's it's a trade off decision for the size of the SDK.

You can see an example of that here, it's only a couple of minutes of work, yields about a 17% reduction in build time (and probably similar in size, but I haven't measured that).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The additional aspect I forgot to mention is that we could simplify the selective import story in TypeScript by manually maintaining "presets", a little like pnpJS does. Where we'd have on one hand the granular modular augmentation (each node level) generated by kiota, and on the other presets that leverage them.
And examples would be

import "msgraphsdk/presets/user";
import "msgraphsdk/presets/teams";

await client.me.joinedTeams.get();
await client.users.byId("foo").joinedTeams.get();

In that example both me and users are here because of the user preset, and joined teams is available under both of them because of the teams preset.

design/service-library.md Show resolved Hide resolved
design/service-library.md Show resolved Hide resolved
design/service-library.md Outdated Show resolved Hide resolved
sebastienlevert and others added 5 commits January 25, 2023 16:22
Co-authored-by: Vincent Biret <vibiret@microsoft.com>
Co-authored-by: Vincent Biret <vibiret@microsoft.com>
Co-authored-by: Vincent Biret <vibiret@microsoft.com>
Co-authored-by: Vincent Biret <vibiret@microsoft.com>
@sebastienlevert
Copy link
Contributor Author

@nikithauc based on the latest progress, can we have an update to these specs, especially around the approach taken for the selective imports? That way we could close this specs PR with the latest engineering decisions. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants