알림: 이 포스트는 순수한 개인의 견해이며, 제가 속해있는 직장의 의견 혹은 입장을 대변하지 않습니다.
ASP.NET Core 애플리케이션에서는 Swashbuckle이라는 엄청난 라이브러리가 있어서 이를 이용하면 정말로 손쉽게 Swagger 문서 및 UI를 사용할 수 있다. 하지만 애저 펑션에서는 아직까지 이런 기능을 제공하지 못하고 있는 상황이다.
애저 펑션 1.x 에서는 프리뷰 형태로 Swagger 문서를 제한적이나마 제공해 왔다. 이와 관련한 포스트도 예전에 썼던 적이 있었는데, 그 포스트에서도 언급했다시피 자동으로 생성해 주는 부분 이외에 추가로 작업해야 할 부분들이 꽤 있기 때문에 여전히 기능이 제한적이다. 게다가 애저 펑션 2.x 으로 올라오면서는 아예 이 프리뷰 기능 조차도 빠져버렸다. 결국 지난 포스트에서는 직접 Swagger 문서를 작성한 후 웹에서 접근 가능한 장소로 업로드한 후 이를 렌더링하는 방법을 소개했다. 하지만, 이 역시도 불편한 것은 사실이다. API 설계 우선 방식(Design-First Approach)으로 접근하는 것은 최초 API를 설계할 당시에는 괜찮을지 모르지만, 운영하는 도중에도 항상 이 설계 우선 방식이 유효하지는 않다. 따라서, Swashbuckle과 비슷한 기능을 하는 라이브러리가 애저 펑션에서도 필요한지라, 직접 만들어 보았다.
애저 펑션 팀 내부에서는 이 기능이 어느 정도의 우선 순위를 갖고 있는지는 모르겠으나, 공식적인 라이브러리 혹은 익스텐션이 나올 때 까지는 이 라이브러리를 꽤 유용하게 사용할 수 있을 것으로 기대한다. 더불어 이 포스트에서는 라이브러리 사용 방법에 대해 간단히 다뤄보고자 한다.
여기에 쓰인 샘플 코드는 이 리포지토리에서 확인할 수 있다.
애저 펑션 버전
이 라이브러리는 애저 펑션의 기본 HTTP 트리거를 이용하므로 1.x 버전과 2.x 버전 모두 사용할 수 있다.
NuGet 패키지 다운로드
이 라이브러리는 현재 NuGet 패키지 리포지토리에서 다운로드 받을 수 있다.
HTTP 트리거 작성
가장 기본적인 HTTP 트리거를 두 개 작성해 보기로 하자. 하나는 GET 메소드를, 다른 하나는 POST 메소드를 구현한다.
// GET Method | |
[FunctionName(nameof(GetSample))] | |
[OpenApiOperation("list", "sample")] | |
[OpenApiParameter("name", In = ParameterLocation.Query, Required = true,Type = typeof(string))] | |
[OpenApiParameter("limit", In = ParameterLocation.Query, Required = false Type = typeof(int))] | |
[OpenApiResponseBody(HttpStatusCode.OK, "application/json", typeo(SampleResponseModel))] | |
public static async Task<IActionResult> GetSample( | |
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "samples")] HttpRequest req, | |
ILogger log) | |
{ | |
... | |
} | |
// POST Method | |
[FunctionName(nameof(PostSample))] | |
[OpenApiOperation("add", "sample")] | |
[OpenApiRequestBody("application/json", typeof(SampleRequestModel))] | |
[OpenApiResponseBody(HttpStatusCode.OK, "application/json", typeo(SampleResponseModel))] | |
public static async Task<IActionResult> PostSample( | |
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "samples")] HttpRequest req, | |
ILogger log) | |
{ | |
... | |
} |
위에서 볼 수 있다시피, Swashbuckle과 비슷한 방식으로 다양한 데코레이터를 사용해서 Open API 문서를 정의했다. OpenAPI.NET 라이브러리를 기반으로 만들어졌기 때문에 이 글을 쓰는 시점에서 데코레이터의 구조는 Open API 3.0.1 스펙을 따른다. 각각의 데코레이터 구조는 이 문서를 참고하도록 한다. 현재 라이브러리 버전 1.1.0은 작동하는 최소한도 수준의 Open API 스펙을 구현했다.
Open API 메타 데이터 설정
Open API 스펙에서는 반드시 Info 객체를 정의해야 하는데, 이 부분은 라이브러리에서 환경 변수를 참조한다. 아래는 local.settings.json
파일의 내용에 대한 예시이다.
{ | |
"IsEncrypted": false, | |
"Values": { | |
... | |
"OpenApi__Info__Version": "2.0.0", | |
"OpenApi__Info__Title": "Open API Sample on Azure Functions", | |
"OpenApi__Info__Description": "A sample API that runs on Azure Functions either 1.x or 2.x using Open API specification.", | |
"OpenApi__Info__TermsOfService": "https://github.com/aliencube/AzureFunctions.Extensions", | |
"OpenApi__Info__Contact__Name": "Aliencube Community", | |
"OpenApi__Info__Contact__Email": "no-reply@aliencube.org", | |
"OpenApi__Info__Contact__Url": "https://github.com/aliencube/AzureFunctions.Extensions/issues", | |
"OpenApi__Info__License__Name": "MIT", | |
"OpenApi__Info__License__Url": "http://opensource.org/licenses/MIT", | |
"OpenApi__ApiKey": "" | |
} | |
} |
애저 펑션 인스턴스의 App Settings 블레이드에는 아래와 같이 값을 입력하면 된다.
OpenApi__Info__Version
: 필수 Open API 문서 버전. 스펙 버전 아님 (예. 2.0.0)OpenApi__Info__Title
: 필수 Open API 문서 제목 (예. Open API Sample on Azure Functions)OpenApi__Info__Description
: Open API 문서 설명 (예. A sample API that runs on Azure Functions either 1.x or 2.x using Open API specification)OpenApi__Info__TermsOfService
: 이용약관 URL (예. https://github.com/aliencube/AzureFunctions.Extensions)OpenApi__Info__Contact__Name
: 담당자 이름 (예. Aliencube Community)OpenApi__Info__Contact__Email
: 담당자 이메일 주소 (예. no-reply@aliencube.org)OpenApi__Info__Contact__Url
: 문의 URL (예. https://github.com/aliencube/AzureFunctions.Extensions/issues)OpenApi__Info__License__Name
: 필수 라이센스 이름 (예. MIT)OpenApi__Info__License__Url
: 라이센스 URL (예. http://opensource.org/licenses/MIT)OpenApi__ApiKey
: Open API 문서 렌더링 엔드포인트의 API Key
Open API 문서 렌더링
위와 같이 두 개의 HTTP 트리거를 작성하고 환경 변수를 설정했다면, 이제는 이를 Open API 문서로 표현할 차례이다. 아래와 같이 HTTP 트리거를 작성한다. OpenApiIgnoreAttribute
데코레이터를 사용해서 이 HTTP 트리거는 Open API 문서에 포함되지 않도록 한다.
[FunctionName(nameof(RenderOpenApiDocument))] | |
[OpenApiIgnore] | |
public static async Task<IActionResult> RenderOpenApiDocument( | |
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "openapi/{version}.{extension}")] HttpRequest req, | |
string version, | |
string extension, | |
ILogger log) | |
{ | |
var ver = GetSpecVersion(version); | |
var ext = GetExtension(extension); | |
var settings = new AppSettings(); | |
var helper = new DocumentHelper(); | |
var document = new Document(helper); | |
var result = await document.InitialiseDocument() | |
.AddMetadata(settings.OpenApiInfo) | |
.AddServer(req, settings.HttpSettings.RoutePrefix) | |
.Build(Assembly.GetExecutingAssembly()) | |
.RenderAsync(ver, ext) | |
.ConfigureAwait(false); | |
var response = new ContentResult() | |
{ | |
Content = result, | |
ContentType = "application/json", | |
StatusCode = (int)HttpStatusCode.OK | |
}; | |
return response; | |
} |
위 펑션 코드는 version
과 extension
바인딩을 허용한다. 아래 렌더링 결과물을 확인해 보면 알 수 있을 것이다. 어떤 URL로 접속하는가에 따라 렌더링하는 문서의 스펙과 포맷이 달라진다.
/api/openapi/v2.json
/api/openapi/v2.yaml
/api/openapi/v3.json
/api/openapi/v3.yaml
Swagger UI 페이지 렌더링
Open API 문서가 만들어졌다면, 이를 이용한 Swagger UI 페이지를 렌더링 할 차례이다. 마찬가지로 OpenApiIgnoreAttribute
데코레이터를 사용해서 이 HTTP 트리거 역시 Open API 문서에 포함되지 않도록 한다. 아래 코드는 swagger.json
엔드포인트를 사용했는데, 이는 위 Open API 문서 렌더링을 위한 엔드포인트를 swagger.json
으로 하드코딩을 해 놓았기 때문이다.
이 포스트를 쓰는 시점에서 Swagger UI 버전은
3.20.5
을 사용했다.
[FunctionName(nameof(RenderSwaggerUI))] | |
[OpenApiIgnore] | |
public static async Task<IActionResult> RenderSwaggerUI( | |
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "swagger/ui")] HttpRequest req, | |
ILogger log) | |
{ | |
var settings = new AppSettings(); | |
var ui = new SwaggerUI(); | |
var result = await ui.AddMetadata(settings.OpenApiInfo) | |
.AddServer(req, settings.HttpSettings.RoutePrefix) | |
.BuildAsync(typeof(SwaggerUI).Assembly) | |
.RenderAsync("swagger.json", settings.SwaggerAuthKey) | |
.ConfigureAwait(false); | |
var response = new ContentResult() | |
{ | |
Content = result, | |
ContentType = "text/html", | |
StatusCode = (int)HttpStatusCode.OK | |
}; | |
return response; | |
} |
이렇게 한 후 /api/swagger/ui
엔드포인트를 웹 브라우저에서 열어보면 아래와 같이 보일 것이다.
여기까지 해서 로컬 개발 환경에서 Open API 문서와 Swagger UI 페이지를 렌더링하는 방법을 알아 봤다. 이제 이를 애저 펑션 인스턴스로 배포를 해 보도록 하자. 애저 펑션 인스턴스에서는 반드시 헤더에 x-functions-key
를 사용하거나 쿼리스트링에 code=xxx
를 사용하는 식으로 Open API 문서 또는 Swagger UI 페이지에 접근한다.
지금까지 Aliencube.AzureFunctions.Extensions.OpenApi 라이브러리를 사용해서 애저 펑션에 Swagger UI를 구현하는 방법에 대해 알아보았다. 앞으로는 애저 펑션에서도 손쉽게 Swagger UI 페이지를 사용할 수 있을 것이다.