-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add developer driven evolution document (#27559)
* Add developer driven evolution document. * Resolve comments * Resolve comments * Resolve comments * Fix typo * Resolve comments * Resolve comments * Resolve comments Co-authored-by: Pan Shao <pashao@microsoft.com>
- Loading branch information
Showing
1 changed file
with
201 additions
and
0 deletions.
There are no files selected for viewing
201 changes: 201 additions & 0 deletions
201
doc/Data Plane Code Generation/DeveloperDrivenEvolution.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
# Developer driven evolution | ||
Convenience API is an additive layer on top of protocol method so it could produce improved user experience. | ||
|
||
To add a convenience API that takes and returns strongly-typed input and output models instead of raw JSON, steps are: | ||
1. [Create the models](#create-the-models) | ||
2. [Create the convenience method with new signature](#create-the-convenience-method-with-new-signature) | ||
- [Replace input parameters](#replace-input-parameters) | ||
- [Replace output value](#replace-output-value) | ||
- [Rename method if needed](#rename-method-if-needed) | ||
3. [Implement the method](#implement-the-method) | ||
- [Implement GET method](#implement-get-method) | ||
- [Implement POST method](#implement-post-method) | ||
- [Implement GET paging method](#implement-get-paging-method) | ||
|
||
## Create the models | ||
Currently the models should be created manually. We will support generate it automatically. So, to avoid the models overwritten, we suggest putting the models under folder {sdk folder}/Models which is the same layer as folder {sdk folder}/Generated. | ||
|
||
After you create the models you want, you also need to add the helper methods mapping between raw response and model. Examples for model `MetricFeedback` (Models/MetricFeedback/MetricFeedback.cs) are: | ||
```C# | ||
namespace Azure.AI.MetricsAdvisor | ||
{ | ||
public partial class MetricFeedback | ||
{ | ||
// Mapping raw response to model | ||
internal static MetricFeedback FromResponse(Response response) | ||
{ | ||
using var document = JsonDocument.Parse(response.Content); | ||
// Add your deserialization logic in DeserializeMetricFeedback | ||
return DeserializeMetricFeedback(document.RootElement); | ||
} | ||
|
||
// Mapping model to raw request | ||
internal static RequestContent ToRequestContent(MetricFeedback metricFeedback) | ||
{ | ||
var content = new Utf8JsonRequestContent(); | ||
content.JsonWriter.WriteObjectValue(metricFeedback); | ||
return content; | ||
} | ||
} | ||
} | ||
``` | ||
|
||
## Create the convenience method with new signature | ||
To create a convenience method on top of the protocol method, you should not modify generated code. The convenience methods should be added in a separate file as partial class of the class containing protocol methods. You could check out the full examples in [Implement the method](#implement-the-method). | ||
|
||
Below is how you could create the convenience signature from protocol method. Here is an example of your generated protocol method. | ||
```C# | ||
public virtual async Task<Response> CreateMetricFeedbackAsync(RequestContent content, RequestContext context = null); | ||
``` | ||
You should do the following steps to create the convenience method signature: | ||
### Replace input parameters | ||
- Replace the parameter `RequestContext` with an optional `CancellationToken` with default value. | ||
- If there is an input parameter `RequestContent`, replace it with a wanted input type (e.g., `MetricFeedback`). | ||
|
||
After these steps, your convenience method signature will be like: | ||
```C# | ||
public virtual async Task<Response> CreateMetricFeedbackAsync(MetricFeedback feedback, CancellationToken cancellationToken = default); | ||
``` | ||
### Replace output value | ||
- Replace the return value `Response` with `Response<T>` (`T` is your wanted output type, e.g., `MetricFeedback`). | ||
|
||
After these steps, your convenience method signature will be like: | ||
```C# | ||
public virtual async Task<Response<MetricFeedback>> CreateMetricFeedbackAsync(MetricFeedback feedback, CancellationToken cancellationToken = default); | ||
``` | ||
### Rename method if needed | ||
Renaming method should follow the rules: | ||
|
||
**It is needed if ambiguous call exists** | ||
|
||
This situation happens when your protocol method and convenience method have the same required paramater list. E.g., | ||
```C# | ||
// protocol method | ||
public virtual async Task<Response> GetMetricFeedbackAsync(string feedId, RequestContext context = null); | ||
// Convenience method | ||
public virtual async Task<Response<MetricFeedback>> GetMetricFeedbackAsync(string feedId, CancellationToken cancellationToken = default); | ||
``` | ||
Ambiguous call exists when you call | ||
```C# | ||
GetMetricFeedbackAsync(feedId); | ||
``` | ||
In this situation, you have to change the convenience name. | ||
|
||
**Suggested rename for most cases** | ||
|
||
If your convenience method has the same parameter list as that of the protocol method, we suggest | ||
- Adding a suffix `Value` to the initial method name as the convenience method name, if the initial method is singular (e.g., change `GetMetricFeedbackAsync` to `GetMetricFeedbackValueAsync`). | ||
```C# | ||
public virtual async Task<Response<MetricFeedback>> GetMetricFeedbackValueAsync(string feedId, CancellationToken cancellationToken = default); | ||
``` | ||
- Adding a suffix `Values` to the initial method name as the convenience method name, if the initial method is plural (e.g., change `GetMetricFeedbacksAsync` to `GetMetricFeedbackValuesAsync`). | ||
```C# | ||
public virtual async Task<Response<MetricFeedback>> GetMetricFeedbackValuesAsync(string feedId, CancellationToken cancellationToken = default); | ||
``` | ||
**If this name doesn't make sense, pick the best name** | ||
|
||
If the suggested rename does not apply to your scenario, you could pick the best name fitting your API. | ||
|
||
## Implement the method | ||
You could call the protocol method inside your convenience method and map the returned raw response to model. See different scenarios and corresponding examples below. | ||
|
||
### Implement GET method | ||
|
||
**Generated code before (Generated/MetricsAdvisorClient.cs):** | ||
``` C# | ||
namespace Azure.AI.MetricsAdvisor | ||
{ | ||
public partial class MetricsAdvisorClient | ||
{ | ||
public virtual async Task<Response> GetMetricFeedbackAsync(Guid feedbackId, RequestContext context = null) | ||
{ | ||
} | ||
} | ||
} | ||
``` | ||
**Improve the GET method ({Other manual folder}/MetricsAdvisorClient.cs):** | ||
``` C# | ||
namespace Azure.AI.MetricsAdvisor | ||
{ | ||
public partial class MetricsAdvisorClient | ||
{ | ||
// Suggest appending value to method name | ||
public virtual async Task<Response<MetricFeedback>> GetMetricFeedbackValueAsync(Guid feedbackId, CancellationToken cancellationToken = default) | ||
{ | ||
// Call protocol method | ||
Response response = await GetMetricFeedbackAsync(feedbackId, new RequestContext() { CancellationToken = cancellationToken }); | ||
|
||
// Calling deserialization helper | ||
MetricFeedback value = MetricFeedback.FromResponse(response); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
### Implement POST method | ||
**Generated code before (Generated/MetricsAdvisorClient.cs):** | ||
``` C# | ||
namespace Azure.AI.MetricsAdvisor | ||
{ | ||
public partial class MetricsAdvisorClient | ||
{ | ||
public virtual async Task<Response> CreateMetricFeedbackAsync(RequestContent content, RequestContext context = null) | ||
{ | ||
} | ||
} | ||
} | ||
``` | ||
**Improve the POST method ({Other manual folder}/MetricsAdvisorClient.cs):** | ||
``` C# | ||
namespace Azure.AI.MetricsAdvisor | ||
{ | ||
public partial class MetricsAdvisorClient | ||
{ | ||
// Can use same method name here | ||
public virtual async Task<Response<MetricFeedback>> CreateMetricFeedbackAsync(MetricFeedback feedback, CancellationToken cancellationToken = default) | ||
{ | ||
// Convert model to binary content | ||
RequestContent requestContent = MetricFeedback.ToRequestContent(feedback); | ||
|
||
// Call protocol method | ||
Response response = await CreateMetricFeedbackAsync(requestContent, new RequestContext() { CancellationToken = cancellationToken }); | ||
|
||
// Calling deserialization helper | ||
MetricFeedback value = MetricFeedback.FromResponse(response); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
## Implement GET paging method | ||
|
||
**Generated code before (Generated/MetricsAdvisorClient.cs):** | ||
``` C# | ||
namespace Azure.AI.MetricsAdvisor | ||
{ | ||
public partial class MetricsAdvisorClient | ||
{ | ||
public virtual AsyncPageable<BinaryData> GetMetricFeedbacksAsync(Guid feedbackId, RequestContext context = null) | ||
{ | ||
} | ||
} | ||
} | ||
``` | ||
**Improve the GET paging method ({Other manual folder}/MetricsAdvisorClient.cs):** | ||
``` C# | ||
namespace Azure.AI.MetricsAdvisor | ||
{ | ||
public partial class MetricsAdvisorClient | ||
{ | ||
// Suggest appending values to a plural method name | ||
public virtual AsyncPageable<MetricFeedback> GetMetricFeedbacksValuesAsync(Guid feedbackId, CancellationToken cancellationToken = default) | ||
{ | ||
// Call protocol method | ||
AsyncPageable<BinaryData> pageableBinaryData = GetMetricFeedbacksAsync(feedbackId, new RequestContext() { CancellationToken = cancellationToken }); | ||
|
||
// Calling deserialization helper | ||
return PageableHelpers.Select(pageableBinaryData, response => ConvertToDataFeeds(DataFeedList.FromResponse(response).Value)); | ||
} | ||
} | ||
} | ||
``` |