API Gateway isteklerini doğrulama & Swagger dokümanının otomatik üretilmesi
Tasarlamış API metotlarının, consumer’lar tarafından kolayca anlaşılabilmesi ve uygulanabilmesi için API dokümanı çok önemlidir. Fakat, API’ın geliştirilmesinin ardından dokümanın hazırlanması bir o kadar sıkıcı ve uğraştırıcı olabilmekte, dokümanı güncel tutmak da ayrı bir sorun olarak karşımıza çıkmaktadır.
Yolda.com’da AWS serverless mimarisi kullanarak geliştirmelerimizi yapıyoruz. Ağırlıklı olarak Typescript ile geliştirdiğimiz Lambda fonksiyonlarımız, API Gateway tarafından tetikleniyor. API dokumasyonunun güncel tutulması bizim için de bir sorundu. Bu sebeple Lambda fonksiyonlarında halihazırda kullandığımız Typescript interface’lerini kullanarak doküman generate edip edemeyeceğimizin araştırmasını yaptık. Böylelikle kod ile doküman her zaman uyumlu olabilecekti. Bu araştırmanın sonucunda API Request validator’ı da kullandığımız, farklı sorunlara da çözüm olan bir çalışma ortaya çıktı.
API Gateway Validator kullanarak request’leri API katmanında doğruladık ve Lambda tarafındaki business logic’lerimizi azalttık, ayrıca gereksiz Lambda request’lerini de engellemiş olduk. Validator tanımında kullanılan modellerimizi ise kodda kullandığımız interface’lerden ürettik. Böylelikle de JsonSchema tanımını ikinci kez yapmamıza gerek kalmadı.Son olarak da, doküman generate edilme kısmını CI/CD süreçlerine dahil ederek her daim güncel API dokümantasyonuna sahip olduk.
İçerik
- Cdk ile API Request Validation
- Typescript Interface’lerden otomatik request modelleri oluşturulması
- Swagger dokümanının export edilmesi
1. Cdk ile API Request Validation
Request validation tanımlayacağımız birer adet GET ve POST metodumuz olduğunu varsayalım.
GET /user : Bu metotta query string parametresi olarak “name” gönderilir, “name” değerine göre sorgulama yapılır, response “User” class tipindedir.
/**
* User class
*/
export class User {
id: string;
name: string;
familyName: string;
company?: string;
age: number;
}
POST /user : POST metodunda ise query parametresi zorunluluğu yoktur ve body yapısının bu şekilde olması beklenir.
/**
* POST /user body
*/
{
name: string;
familyName: string;
company?: string;
age: number;
}
API Gateway’de request validator 3 çeşittir:
- validate body: sadece body doğrulaması
- validate query string parameters ve headers: sadece parametre doğrulaması
- validate body, query string parameters and headers: hem body hem de parametre doğrulaması
GET /user metodu için query string validator ekleyeceğiz. Öncelikle “name” in zorunlu olduğu Request Validator tanımını yapacağız ve Validator’ı metotta kullanılacak MethodOptions’a referans göstereceğiz.
/**
* Query string parameters validation
*/
const requestValidator = new RequestValidator(
this,
`requestValidatorId`,
{
restApi: restApi,
requestValidatorName: `${this.constructor.name}Validator`,
validateRequestParameters: true,
validateRequestBody: false
}
);const methodOptions: MethodOptions = {
apiKeyRequired: true,
authorizationType: AuthorizationType.CUSTOM,
authorizer: {
authorizerId: customAuthorizer.ref,
}
requestValidator: requestValidator,
requestParameters: {
"method.request.querystring.name": true,
}
}
GET metodunu methodOptions ile oluşturduğumuzda artık atılan istekte name parametresi yok ise lambda tetiklenmeden response API Gateway tarafından 400 — BAD_REQUEST_PARAMETER olarak dönecektir.
POST /user metodu için ise body doğrulaması yapalım. Request Validator’ı body doğrulaması yapacak şekilde tanımlayalım.
/**
* body validation
*/
const requestValidator = new RequestValidator(
this,
`requestValidatorId`,
{
restApi: restApi,
requestValidatorName: `${this.constructor.name}Validator`,
validateRequestParameters: false,
validateRequestBody: true,
}
)
MethodOptions tanımlamadan önce body doğrulamasının yapılacağı model tanımını yapmalıyız. (Sonraki adımlarda json schema, var olan class ile üretilecek)
/**
* Body validator model definition
*/
const jsonSchema: JsonSchema = {
schema: JsonSchemaVersion.DRAFT4,
type: JsonSchemaType.OBJECT,
properties: {
name: { type: JsonSchemaType.STRING },
familyName: { type: JsonSchemaType.STRING },
company: { type: JsonSchemaType.STRING },
age: { type: JsonSchemaType.NUMBER },
},
required: ["name", "familyName", "age"]
}const requestBodyModel: Model = new Model(scope,
`${this.constructor.name}Request`,
{
restApi,
contentType: "application/json",
description: `To validate ${this.constructor.name} body`,
modelName: `${this.constructor.name}Request`,
schema: jsonSchema
}
)const methodOptions: MethodOptions = {
apiKeyRequired: true,
authorizationType: AuthorizationType.CUSTOM,
authorizer: {
authorizerId: customAuthorizer.ref
},
requestValidator,
requestModels: {
"application/json": requestBodyModel,
}
}
Post isteğimizi uyumsuz body objesi ile test edelim ve API Gateway response’larına bakalım.
2. Typescript class’lardan otomatik request modelleri oluşturulması
GET ve POST metotlarına validator ve modelleri ekledik. Fakat oluşturduğumuz modellerde lambda içinde kullandığımız class’ın benzerini tekrar JsonSchema olarak yazdık. Hem aynı yapıyı ikinci kez yazmamak hem de değişikliklerde uyumsuzlukların önüne geçmek için Lambda içinde kullandığımız class’ları jsonschema’ya dönüştürüp kullanacağız.
Bunun için farklı çözümler mevcut, herhangi biri kullanılabilir:
- Kendi class → JsonSchema dönüştürücünüzü yazabilirsiniz. Örnek çalışma için: https://github.com/martzcodes/blog-ts-request-validation/blob/main/src/util/ast.ts
- Typescript class’ı jsonSchema’ya dönüştürmek için npm kütüphaneleri mevcut. Bunlardan ikisi “typescript-json-schema” ve “class-validator-jsonschema”
typescript-json-schema: Class’ların tanımlı olduğu path parametre olarak veriliyor. Path’deki tüm classlar jsonSchema’ya dönüştürülüyor. Model oluşturulmadan önce kullanılacak class ismi verilerek JsonSchema elde edilir.
const settings: TJS.PartialArgs = {
required: true,
};
const compilerOptions: TJS.CompilerOptions = {
strictNullChecks: true,
};const basePath = "./my-dir";const program = TJS.getProgramFromFiles(
[resolve("my-file.ts")],
compilerOptions,
basePath
);const generator = TJS.buildGenerator(program, settings);
const mySchema: JsonSchema = generator!.getSchemaForSymbol("MyType") as JsonSchema;
Ya da proje build script’i içine schema’ların generate edilip, export edilmesini sağlayan komutu ekleyebilirsiniz.
typescript-json-schema “tsconfig.json” “*” — include “myPath/*.ts” — required true — out schema.json
Böylelikle proje her build edildiğinde schema.json tekrar oluşturulur. Model oluşturulurken ise schema.json dosyası okunarak istenilen schema’ya erişilebilir.
typescript-json-schema “?” karakteri ile opsiyonel olarak belirtilen propertyleri de dikkate alarak required array’ini oluşturur. Ayrıca property decoratorları ile json schema validation da eklenebiliyor.
/**
* @minLength 10
*/
name: string;
class-validator-jsonschema: Bu paket ile path belirtmeye gerek kalmadan jsonschema’ya dönüştürülmek istenen class parametre olarak verilerek schema elde edilebiliyor. Fakat required field’lar ya da schema validasyonu için class-validator decorator’lerinin kullanılması gerekiyor. Opsiyonel alanlar için @IsOptional() kullanılmalı.
@IsOptional()
company: string;
class-validator’ü zaten kullanıyor iseniz pratik bir çözüm olabilir.
3. Swagger dokümanının export edilmesi
Buraya kadar API metotlarına ait tüm modellerimizi var olan class’ları kullanarak oluşturduk ve request validator’ları tanımladık. Şimdi sıra güncel swagger dokümanının generate edilmesinde. Bu kısım için CI/CD süreçlerine yeni bir stage ekledik.
return BuildSpec.fromObject({
"version": 0.2,
"env": {
"secrets-manager": {
"STOPLIGHT_TOKEN": "stoplightTokenKey"
}
},
"phases": {
"install": {
"runtime-versions": {
"nodejs": 14
},
"commands": [
"npm install @stoplight/cli@4"
]
},
"build": {
"commands": [
"mkdir document-output",
"export API_ID=$(aws apigateway get-rest-apis --query 'items[?name==`apiName`].[id]' --output text)",
"aws apigateway get-export --rest-api-id $API_ID --stage-name 'live' --export-type 'swagger' document-output/apiSwagger.json",
"npx @stoplight/cli@4 push --directory document-output --ci-token $STOPLIGHT_TOKEN --branch apiName --verbose"
]
}
}
});
Yeni eklenen stage API deploy edilmesinin ardından çalıştırılıyor. Öncelikle API name ile filtrelenerek API Id elde ediliyor. Ardından ilgili API için doküman export edilip stoplight’a upload ediliyor.
Üç aşamayı bir bütün olarak düşündüğümüzde, kod içinde kullanılan class’lar esas alınarak API katmanında validasyonun ve doküman üretilmesinin otomatize edildiği bir yapı kurulmuş oldu.
Okuduğunuz için teşekkürler!