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

Add a pattern for modeling entity templates #536

Open
wants to merge 34 commits into
base: vNext
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
42bf48c
Create entity-template.md
corranrogue9 Nov 17, 2023
12d87d4
Update entity-template.md
corranrogue9 Nov 17, 2023
ccc0220
Update entity-template.md
corranrogue9 Nov 17, 2023
9ea2260
Update entity-template.md
corranrogue9 Nov 17, 2023
33abb4a
Update entity-template.md
corranrogue9 Nov 17, 2023
2c50645
Update entity-template.md
corranrogue9 Nov 18, 2023
1cfaf93
Update entity-template.md
corranrogue9 Dec 19, 2023
f5189b6
Update entity-template.md
corranrogue9 Dec 19, 2023
7f485cf
Update entity-template.md
corranrogue9 Dec 19, 2023
af8eaec
Update entity-template.md
corranrogue9 Dec 19, 2023
2d9ad3b
Update entity-template.md
corranrogue9 Dec 19, 2023
7bc33a0
Update entity-template.md
corranrogue9 Dec 19, 2023
e08ca19
Update entity-template.md
corranrogue9 Dec 20, 2023
6a8a7d2
Update entity-template.md
corranrogue9 Dec 20, 2023
2c0ab5d
Update entity-template.md
corranrogue9 Dec 21, 2023
5d800f3
Update entity-template.md
corranrogue9 Dec 21, 2023
d9cbce6
Update entity-template.md
corranrogue9 Dec 21, 2023
a14d882
Update entity-template.md
corranrogue9 Dec 21, 2023
1df6976
Update entity-template.md
corranrogue9 Dec 21, 2023
460d9ac
Update entity-template.md
corranrogue9 Dec 21, 2023
746b2c7
Update entity-template.md
corranrogue9 Dec 21, 2023
5c98aee
Update entity-template.md
corranrogue9 Dec 21, 2023
86291d9
Update entity-template.md
corranrogue9 Dec 21, 2023
3d73f0f
Update entity-template.md
corranrogue9 Dec 21, 2023
cd6d358
Update entity-template.md
corranrogue9 Dec 21, 2023
a2c6e2a
Update entity-template.md
corranrogue9 Dec 22, 2023
0ed3553
Update entity-template.md
corranrogue9 Jan 23, 2024
9f048ca
Update entity-template.md
corranrogue9 Jan 23, 2024
bf9212b
Update entity-template.md
corranrogue9 Jan 23, 2024
6211839
Update entity-template.md
corranrogue9 Jan 23, 2024
b08dd4c
Update entity-template.md
corranrogue9 Jan 23, 2024
5361919
Update entity-template.md
corranrogue9 Jan 24, 2024
d9bfca3
Update entity-template.md
corranrogue9 Mar 21, 2024
55524cb
Update entity-template.md
corranrogue9 Mar 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
309 changes: 309 additions & 0 deletions graph/patterns/entity-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
# Entity template

Microsoft Graph API Design Pattern

*An entity template is a template which can be used to create a new instance of an entity.*

## Problem

A customer wants to create instances of an entity that all start from the same basic data, and this data needs to be shared across clients over time.

## Solution

### Introduce a new entity type that is a template for the entity that the customer is trying to create.
For an existing `foo` entity:
```xml
<EntityContainer Name="Container">
<EntitySet Name="foos" EntityType="self.foo" />
</EntityContainer>
<EntityType Name="foo">
<Key>
<PropertyRef Name="id" />
</Key>
<Property Name="id" Type="Edm.String" Nullable="false" />
<Property Name="fizz" Type="self.fizz" />
<Property Name="buzz" Type="self.buzz" />
</EntityType>
```

a "template" entity is defined for `foo`:
```xml
<EntityType Name="fooTemplate">
<Key>
<PropertyRef Name="id" />
</Key>
<Property Name="id" Type="Edm.String" Nullable="false" />
<Property Name="fizz" Type="self.fizz" />
<Property Name="buzz" Type="self.buzz" />
</EntityType>
<EntityContainer Name="Container">
<EntitySet Name="fooTemplates" EntityType="self.fooTemplate" />
</EntityContainer>
```

An action should also be introduced that will create the `foo` based on the `fooTemplate`:
```xml
<!--because actions cannot be overloaded, and for a consistent client experience, the name "createFromTemplate" should be used instead of other names-->
<Action Name="createFromTemplate" IsBound="true">
<Parameter Name="bindingParameter" Type="Collection(self.foo)" Nullable="false" />
<Parameter Name="template" Type="self.fooTemplate" Nullable="false" />
<ReturnType Type="self.foo" />
</Action>
```

A client can now create a `fooTemplate`, something like:
```http
POST /fooTemplates
{
"fizz": {
// fizz properties here
},
"buzz": null
}

HTTP/1.1 201 Created
Location: /fooTemplates/{templateId1}

{
"id": "{templateId1}",
"fizz": {
// fizz properties here
},
"buzz": null
}
```

This template can then be used to create a `foo`:
```http
POST /foos/createFromTemplate
{
"template": {
"@id": "/fooTemplates/{templateId1}"
}
}

HTTP/1.1 201 Created
Location: /foos/{fooId1}

{
"id": "{fooId1}",
"fizz": {
...
},
"buzz": {
...
}
}
```

You can find an example of how to implement this using OData WebApi [here](https://github.com/OData/AspNetCoreOData/commit/d9fa5db0177e1ee8e71a87641ad097dae3ee5e5d).

### Add a new property to an existing entity and update its associated template entity

A property `frob` may be added to the `foo` entity:
```xml
<EntityType Name="foo">
...
<Property Name="frob" Type="self.frob" Nullable="true" /> <!--NOTE: this property is nullable for illustrative purposes; new properties do not need to be marked nullable-->
</EntityType>
```

Likely, clients will want an analogous property on the `fooTemplate`.
Because the default value of such a property on `foo` is ambiguous (the default may be `null`, some static value, or a service-generated value only known based on the overall state at runtime), the `fooTemplate` needs a clear way to indicate whether a value was specified for the `frob` property.
This is done with the use of the `notProvided` instance annotation.
`frob` will be defined on `fooTemplate` as usual:
```xml
<EntityType Name="fooTemplate">
...
<Property Name="frob" Type="self.frob" Nullable="true" /> <!--NOTE: this property is nullable for illustrative purposes; new properties do not need to be marked nullable-->
</EntityType>
```

Now, existing templates will return the `notProvided` instance annotation for `frob`:
```http
GET /fooTemplates/{templateId1}

HTTP/1.1 200 OK
{
"id": "{templateId1}",
"fizz": {
// fizz properties here
},
"buzz": null,
"frob@notProvided": true
}
```

A new template can be created in the following ways

#### a value for `frob` is specified by the client

The client creates a new template with a value for `frob`:
```http
POST /fooTemplates
{
"fizz": {
// fizz properties here
},
"buzz": {
// buzz properties here
},
"frob": {
// frob properties here
}
}

HTTP/1.1 201 Created
Location: /fooTemplates/{templateId2}

{
"id": "{templateId2}",
"fizz": {
// fizz properties here
},
"buzz": {
// buzz properties here
},
"frob": {
// frob properties here
}
}
```

The client then uses that template to create a `foo`:
```http
POST /foos/createFromTemplate
{
"[email protected]": "/fooTemplates/{templateId2}"
}

HTTP/1.1 201 Created
Location: /foos/{fooId2}

{
"id": "{fooId2}",
"fizz": {
// fizz properties here
},
"buzz": {
// buzz properties here
},
"frob": {
// frob properties here
}
}
```

#### `null` is explicitly specified for `frob`

The client creates a new template specifying `null` for `frob`:
```http
POST /fooTemplates
{
"fizz": {
// fizz properties here
},
"buzz": {
// buzz properties here
},
"frob": null
}

HTTP/1.1 201 Created
Location: /fooTemplates/{templateId3}

{
"id": "{templateId3}",
"fizz": {
// fizz properties here
},
"buzz": {
// buzz properties here
},
"frob": null
}
```

The client then uses that template to create a `foo`:
```http
POST /foos/createFromTemplate
{
"[email protected]": "/fooTemplates/{templateId3}"
}

HTTP/1.1 201 Created
Location: /foos/{fooId3}

{
"id": "{fooId3}",
"fizz": {
// fizz properties here
},
"buzz": {
// buzz properties here
},
"frob": null
}
```

#### no value is specified for `frob`

The client creates a new template without specifying any value for `frob`:
```http
POST /fooTemplates
{
"fizz": {
// fizz properties here
},
"buzz": {
// buzz properties here
}
}

HTTP/1.1 201 Created
Location: /fooTemplates/{templateId4}

{
"id": "{templateId4}",
"fizz": {
// fizz properties here
},
"buzz": {
// buzz properties here
},
"frob@notProvided": true
}
```

The client then uses that template to create a `foo`:
```http
POST /foos/createFromTemplate
{
"[email protected]": "/fooTemplates/{templateId4}"
}

HTTP/1.1 201 Created
Location: /foos/{fooId4}

{
"id": "{fooId3}",
"fizz": {
// fizz properties here
},
"buzz": {
// buzz properties here
},
"frob": // server-generated default value for frob here
}
```

You can find an example of how to implement this using OData WebApi [here](https://github.com/OData/AspNetCoreOData/commit/cf5583a5fd2c8acedf178df90d6dd6cab9820e62).

## When to use this pattern

This pattern should be used whenever customers need to create instances of an entity using the same basic outline, but where those instances have their own, individual lifecycle.

## Issues and considerations

Templates for relatively small entities should be avoided.
Generally speaking, managing templates should not be more costly for clients than directly managing the entities themselves.