心配するのをやめて、安らかなAPIにメタデータを提供し始めた方法





パブリックAPIを作成している場合、そのドキュメントの問題に直面している可能性が高いです。 企業は、開発者向けの特別なポータルを作成し、ドキュメントを読んで議論したり、お気に入りのプログラミング言語のクライアントライブラリをダウンロードしたりできます。



このようなリソースのサポートは(特にAPIが積極的に開発されている場合)、かなり労働集約的な問題です。 変更を加えると、ドキュメントを実際の実装と同期させる必要があり、これは面倒です。 同期の構成:



スタートアップapiary.ioのメンバーは2番目のポイントを自動化することを提案し、特別なドメイン固有言語(DSL)でドキュメントを作成し、APIのプロキシを使用してリクエストを記録し、記載されているすべてが正しいことを定期的にチェックする機会を提供します。 ただし、この場合、すべてのドキュメントを自分で作成する必要があります。これは、コードでインターフェイスを既に説明している可能性が高いため、余分なようです。



もちろん、コードからリクエストとレスポンスの記述の形でインターフェースを抽出する普遍的な方法はありませんが、ルーティングとクエリの実行について合意したフレームワークを使用する場合、そのような情報を取得できます。 さらに、そのような説明は不要であり、クライアント自身がRESTリソースの使用方法を理解し、使用するルートリソースとメディアタイプのURLのみを知っている必要がある という意見があります。 しかし、私はこのアプローチを使用する単一の深刻なパブリックAPIを見たことはありません。



ドキュメントを自動的に生成するには、 WSDLのようなメタデータを記述するための形式が必要になりますが、RESTに関する記述が必要です。



いくつかのオプションがあります:







私は後者のオプションを選択しました。これは、独自の認証/承認、単位時間あたりのリクエスト数の制限など、実装のすべての機能を考慮することができるためです。 さらに、上記のすべてのソリューションで発生するように、1つのドキュメントに自然言語のメタデータと説明を公開するというアイデアはあまり好きではありません(ローカリゼーションはどうですか?)。

ドキュメントの生成に加えて、メタデータを使用してAPIのクライアントコードを生成できます。 このようなクライアントはリファレンス実装になり、APIのテストに使用できます。



実装



さらに、 ASP.NET WebAPIから遠く離れている人にとっては面白くないでしょう。 したがって、このプラットフォームにAPIがあり、メタデータを公開する必要があります。 最初に、説明がメタデータに含まれるアクションとタイプをマークする属性が必要です。



[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] public class MetaAttribute : Attribute { }
      
      







次に、APIで使用可能な型スキーム( json schemaのようなものですが、より単純なもの)を返すコントローラーを作成しましょう。



  public class TypeMetadataController : ApiController { private readonly Assembly typeAssembly; public TypeMetadataController(Assembly typeAssembly) { this.typeAssembly = typeAssembly; } [OutputCache] public IEnumerable<ApiType> Get() { return this.typeAssembly .GetTypes() .Where(t => Attribute.IsDefined(t, typeof(MetaAttribute))) .Select(GetApiType); } [OutputCache] public ApiType Get(String name) { var type = this.Get().FirstOrDefault(t => t.Name == name); if (type == null) throw new ResourceNotFoundException<ApiType, String>(name); return type; } ApiType GetApiType(Type type) { var dataContractAttribute = type.GetCustomAttribute<DataContractAttribute>(); return new ApiType { Name = dataContractAttribute != null ? dataContractAttribute.Name : type.Name, DocumentationArticleId = dataContractAttribute != null ? dataContractAttribute.Name : type.Name, Properties = type.GetMembers() .Where(p => p.IsDefined(typeof(DataMemberAttribute), false)) .Select(p => { var dataMemberAttribute = p.GetCustomAttributes(typeof (DataMemberAttribute), false).First() as DataMemberAttribute; return new ApiTypeProperty { Name = dataMemberAttribute != null ? dataMemberAttribute.Name : p.Name, Type = ApiType.GetTypeName(GetMemberUnderlyingType(p)), DocumentationArticleId = String.Format("{0}.{1}", dataContractAttribute != null ? dataContractAttribute.Name : type.Name, dataMemberAttribute != null ? dataMemberAttribute.Name : p.Name) }; } ).ToList() }; } static Type GetMemberUnderlyingType(MemberInfo member) { switch (member.MemberType) { case MemberTypes.Field: return ((FieldInfo)member).FieldType; case MemberTypes.Property: return ((PropertyInfo)member).PropertyType; default: throw new ArgumentException("MemberInfo must be if type FieldInfo or PropertyInfo", "member"); } } }
      
      







実行時に型が変更されることはほとんどないため、結果をキャッシュします。

APIが処理できるリクエストに関する情報を取得するには、 IApiExplorerを使用できます。



  public class ResourceMetadataController : ApiController { private readonly IApiExplorer apiExplorer; public ResourceMetadataController(IApiExplorer apiExplorer) { this.apiExplorer = apiExplorer; } [OutputCache] public IEnumerable<ApiResource> Get() { var controllers = this.apiExplorer .ApiDescriptions .Where(x => x.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<MetaAttribute>().Any() || x.ActionDescriptor.GetCustomAttributes<MetaAttribute>().Any()) .GroupBy(x => x.ActionDescriptor.ControllerDescriptor.ControllerName) .Select(x => x.First().ActionDescriptor.ControllerDescriptor.ControllerName) .ToList(); return controllers.Select(GetApiResourceMetadata).ToList(); } ApiResource GetApiResourceMetadata(string controller) { var apis = this.apiExplorer .ApiDescriptions .Where(x => x.ActionDescriptor.ControllerDescriptor.ControllerName == controller && ( x.ActionDescriptor.GetCustomAttributes<MetaAttribute>().Any() || x.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<MetaAttribute>().Any() ) ).GroupBy(x => x.ActionDescriptor); return new ApiResource { Name = controller, Requests = apis.Select(g => this.GetApiRequest(g.First(), g.Select(d => d.RelativePath))).ToList(), DocumentationArticleId = controller }; } ApiRequest GetApiRequest(ApiDescription api, IEnumerable<String> uris) { return new ApiRequest { Name = api.ActionDescriptor.ActionName, Uris = uris.ToArray(), DocumentationArticleId = String.Format("{0}.{1}", api.ActionDescriptor.ControllerDescriptor.ControllerName, api.ActionDescriptor.ActionName), Method = api.HttpMethod.Method, Parameters = api.ParameterDescriptions.Select( parameter => new ApiRequestParameter { Name = parameter.Name, DocumentationArticleId = String.Format("{0}.{1}.{2}", api.ActionDescriptor.ControllerDescriptor.ControllerName, api.ActionDescriptor.ActionName, parameter.Name), Source = parameter.Source.ToString().ToLower().Replace("from",""), Type = ApiType.GetTypeName(parameter.ParameterDescriptor.ParameterType) }).ToList(), ResponseType = ApiType.GetTypeName(api.ActionDescriptor.ReturnType), RequiresAuthorization = api.ActionDescriptor.GetCustomAttributes<RequiresAuthorization>().Any() }; } }
      
      







返されるすべてのオブジェクトには、フィールド`DocumentationArticleId`があります-これは、jsonファイルやデータベースなど、メタデータとは別に保存される要素のドキュメント記事の識別子です。



あとは、ドキュメントを表示および編集する1ページのアプリケーションを作成するだけです。







残りのコードはGitHubで入手できます



All Articles