Stop writing YAML for OpenAPI, use a compact DSL and save time and typing.
Because writing YAML by hand is not fun… You can use ZenWave Domain Language models as IDL to generate OpenAPI v3 with ZenWave SDK.
Imagine being able to quickly define an API in a more concise and readable way, without losing the rigor of an OpenAPI specification.
This article explores how you can leverage ZenWave Domain Language (ZDL), a compact developer-friendly DSL, to generate OpenAPI definitions, simplifying the process of creating API documentation, reducing errors, and improving developer productivity.
Using a compact DSL might be the key to making your API workflow more efficient.
ZenWave Domain Language (ZDL)
Inspired by JHipster JDL, ZDL is a language for describing DDD Bounded Contexts, including domain entities and their relationships, services, commands, events and business policies… for Event-Driven Architectures.
It’s designed to be compact, readable and expressive. Developer friendly, and machine friendly. It works well as an Ubiquitous Language format.
It can also be used as an IDL for authoring OpenAPI (and AsyncAPI) definition files.
Creating a ZDL for authoring an OpenAPI definition
- As a minimum requirement, you need a
serviceand anaggregate entityfor this service. - You can use this
entityas request and response objects or you can define separate DTOs for this purpose usinginputandoutputentities. - You can also define and reference
enumsandrelationshipsbetween entities. Nested entities and arrays also work. - Lastly you need to define the service methods and their parameters and annotate them using
@rest,@post,@get,@put,@delete,@paginated,@inlineannotations.
@aggregate
entity PaymentMethod {
type PaymentMethodType required
cardNumber String required minlength(16) maxlength(16)
}
enum PaymentMethodType { VISA(1), MASTERCARD(2) }
@rest("/payment-methods")
service PaymentsService for (PaymentMethod) {
@post
doSomethingWithANewPayment(PaymentMethod) PaymentMethod
@put("/{id}")
doSomethingWithAnExistingPayment(id, PaymentMethod) PaymentMethod?
}NOTE: service methods only accept two kind of parameters: id and payload(that will map to the request body), but you can use @inline to expand input fields as request path parameters (see example below).
Checkout the ZDL documentation for more details about command methods.
Install ZenWave SDK Using JBang
Install an evergreen self updating ZenWave SDK CLI using JBang:
jbang alias add --fresh --name=zw release@zenwave360/zenwave-sdkFollowing these instructions for complete details about JBang and IntelliJ Editor: https://www.zenwave360.io/docs/getting-started/
Now you can use jbang zw to generate a complete OpenAPI definition file from your ZDL model.
jbang zw -p ZDLToOpenAPIPlugin \
specFile=model.zdl \
idType=integer \
idTypeFormat=int64 \
targetFolder=. \
targetFile=payments-openapi.ymlOr use ZenWave ZDL Editor for IntelliJ configuring the generator plugin on top of your zdl file:
config {
plugins {
/** Use ZenWave Editor for IntelliJ IDEA to run this */
ZDLToOpenAPIPlugin {
idType integer
idTypeFormat int64
targetFolder "."
targetFile "openapi.yml"
}
}
}Then, check the generated OpenAPI definition file payments-openapi.yml, and see for yourself how much typing you saved!
Expanding fields as request path parameters
You can use @inline ìnputs to expand fields as request path parameters (and service method parameters).
@inline // expand fields as request parameters
input PaymentMethodInput {
cardNumber String
paymentMethod PaymentMethod
}
@rest("/customers")
service PaymentsService for (PaymentMethod) {
@put("/{paymentMethodId}/cardNumber/{cardNumber}") // see example below to specify param types
updatePaymentMethodByCardNumber(id, PaymentMethodInput) PaymentMethod?
}It will pick the first parameter from the entity id and the remaining parameters will be configured as string
But you can override the path params with configuration, see complete example below.
Complete ZDL Example
config {
plugins {
/** Use ZenWave Editor for IntelliJ IDEA to run this */
ZDLToOpenAPIPlugin {
idType integer
idTypeFormat int64
targetFolder "."
targetFile "openapi.yml"
}
}
}
@aggregate
entity Customer {
name String required maxlength(254) /** Customer name */
email String required maxlength(254) pattern(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/)
/** Customer Addresses can be stored in a JSON column in the database. */
@json addresses Address[] minlength(1) maxlength(5) {
addressId Long
street String required maxlength(254)
city String required maxlength(254)
}
}
entity PaymentMethod {
type PaymentMethodType required
cardNumber String required
}
enum PaymentMethodType { VISA(1), MASTERCARD(2) }
relationship OneToMany {
Customer{paymentMethods required maxlength(3)} to PaymentMethod{customer required}
}
// you can create 'inputs' as dtos for your service methods, or use entities directly
input CustomerSearchCriteria {
name String
email String
city String
state String
}
@inline // expand fields as request parameters (and service method parameters)
input AddressInput {
addressId Long
address Address
}
@rest("/customers")
service CustomerService for (Customer) {
@post
createCustomer(Customer) Customer
@get("/{id}")
getCustomer(id) Customer?
@put("/{id}")
updateCustomer(id, Customer) Customer?
@put({ path: "/{customerId}/address/{addressId}", params: {addressId: Long} }) // specify param types
updateCustomerAddress(id, AddressInput) Customer?
@delete("/{id}")
deleteCustomer(id)
@post("/search")
@paginated
searchCustomers(CustomerSearchCriteria) Customer[]
}Run:
jbang zw -p ZDLToOpenAPIPlugin \
specFile=customers-model.zdl \
idType=integer \
idTypeFormat=int64 \
targetFolder=. \
targetFile=openapi.ymlAnd get surprised by the amount of YAML typing you saved!
Happy coding! 🚀