visit
To generate documentation in the case of an Event-Driven architecture, there is . AsyncAPI is an open-source initiative that seeks to improve the current state of Event-Driven Architecture (EDA). AsyncApi has several Java tools that allow you to generate documentation from code. In this article, I described how to set up one of these springwolf tools.
{
"service": {
"serviceVersion": "2.0.0",
"info": {
//block with service info
},
"servers": {
"kafka": {
//describe of kafka connection
}
},
"channels": {
"kafka-channel": {
"subscribe": {
//...
"message": {
"oneOf": [
{
"name": "pckg.test.TestEvent",
"title": "TestEvent",
"payload": {
"$ref": "#/components/schemas/TestEvent"
}
}
]
}
},
//...
}
},
"components": {
"schemas": {
"TestEvent": {
//jsonschema of component
}
}
}
}
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class TestEvent implements Serializable {
private String id;
private LocalDateTime occuredOn;
private TestEvent.ValueType valueType;
private Map<String, Boolean> flags;
private String value;
public enum ValueType {
STRING("STRING"),
BOOLEAN("BOOLEAN"),
INTEGER("INTEGER"),
DOUBLE("DOUBLE");
private final String value;
public ValueType(String value) {
this.value = value;
}
}
}
{
"service": {
//...
"components": {
"schemas": {
"TestEvent": {
"type": "object",
"properties": {
"id": {
"type": "string",
"exampleSetFlag": false
},
"occuredOn": {
"type": "string",
"format": "date-time",
"exampleSetFlag": false
},
"valueType": {
"type": "string",
"exampleSetFlag": false,
"enum": [
"STRING",
"BOOLEAN",
"INTEGER",
"DOUBLE"
]
},
"flags": {
"type": "object",
"additionalProperties": {
"type": "boolean",
"exampleSetFlag": false
},
"exampleSetFlag": false
},
"value": {
"type": "string",
"exampleSetFlag": false
}
},
"example": {
"id": "string",
"occuredOn": "2015-07-20T15:49:04",
"valueType": "STRING",
"flags": {
"additionalProp1": true,
"additionalProp2": true,
"additionalProp3": true
}
},
"exampleSetFlag": true
}
}
}
}
}
When generating DTO classes, we will get the following class structure. You can see that Enum is processed as in the original version, however, the collection of type Map<String, Boolean> has turned into a separate class Flags and the entire value of the collection itself will fall into the Flags.additionalProperties field.
package pckg.test;
// import
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"id",
"occuredOn",
"valueType",
"flags",
"value"
})
@Generated("jsonschema2pojo")
public class TestEvent implements Serializable
{
@JsonProperty("id")
private String id;
@JsonProperty("occuredOn")
private LocalDateTime occuredOn;
@JsonProperty("valueType")
private TestEvent.ValueType valueType;
@JsonProperty("flags")
private Flags flags;
@JsonProperty("value")
private String value;
@JsonIgnore
private Map<String, Object> additionalProperties = new LinkedHashMap<String, Object>();
private final static long serialVersionUID = 73777748L;
// Getters ans Setters
@Generated("jsonschema2pojo")
public enum ValueType {
STRING("STRING"),
BOOLEAN("BOOLEAN"),
INTEGER("INTEGER"),
DOUBLE("DOUBLE");
private final String value;
private final static Map<String, TestEvent.ValueType> CONSTANTS = new HashMap<String, TestEvent.ValueType>();
static {
for (TestEvent.ValueType c: values()) {
CONSTANTS.put(c.value, c);
}
}
ValueType(String value) {
this.value = value;
}
@Override
public String toString() {
return this.value;
}
@JsonValue
public String value() {
return this.value;
}
@JsonCreator
public static TestEvent.ValueType fromValue(String value) {
TestEvent.ValueType constant = CONSTANTS.get(value);
if (constant == null) {
throw new IllegalArgumentException(value);
} else {
return constant;
}
}
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
})
@Generated("jsonschema2pojo")
public class Flags implements Serializable
{
@JsonIgnore
private Map<String, Boolean> additionalProperties = new LinkedHashMap<String, Boolean>();
private final static long serialVersionUID = 74717740L;
//getters and setters
}
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter
@Setter(AccessLevel.PROTECTED)
@EqualsAndHashCode
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type",
visible = true,
defaultImpl = ChangedEvent.class
)
@JsonSubTypes(value = {
@JsonSubTypes.Type(name = ChangedEvent.type, value = ChangedEvent.class),
@JsonSubTypes.Type(name = DeletedEvent.type, value = DeletedEvent.class)
})
@JsonIgnoreProperties(ignoreUnknown = true)
@Schema(oneOf = {ChangedEvent.class, DeletedEvent.class},
discriminatorProperty = "type",
discriminatorMapping = {
@DiscriminatorMapping(value = ChangedEvent.type, schema = ChangedEvent.class),
@DiscriminatorMapping(value = DeletedEvent.type, schema = DeletedEvent.class),
})
public abstract class DomainEvent {
@Schema(required = true, nullable = false)
private String id;
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime occuredOn = LocalDateTime.now();
public abstract String getType();
}
/**
* Subtype ChangedEvent
*/
public class ChangedEvent
extends DomainEvent
implements Serializable
{
public static final String type = "CHANGED_EVENT";
private String valueId;
private String value;
}
/**
* Subtype DeletedEvent
*/
public class DeletedEvent
extends DomainEvent
implements Serializable
{
public static final String type = "DELETED_EVENT";
private String valueId;
}
"components": {
"schemas": {
"ChangedEvent": {
"type": "object",
"properties": {
"id": {
"type": "string",
"exampleSetFlag": false
},
"occuredOn": {
"type": "string",
"format": "date-time",
"exampleSetFlag": false
},
"value": {
"type": "string",
"exampleSetFlag": false
},
"valueId": {
"type": "string",
"exampleSetFlag": false
},
"type": {
"type": "string",
"exampleSetFlag": false
}
},
"example": {
"id": "string",
"occuredOn": "2015-07-20T15:49:04",
"value": "string",
"valueId": "string",
"type": "CHANGED_EVENT"
},
"exampleSetFlag": true
},
"DeletedEvent": {
"type": "object",
"properties": {
"id": {
"type": "string",
"exampleSetFlag": false
},
"occuredOn": {
"type": "string",
"format": "date-time",
"exampleSetFlag": false
},
"valueId": {
"type": "string",
"exampleSetFlag": false
},
"type": {
"type": "string",
"exampleSetFlag": false
}
},
"example": {
"id": "string",
"occuredOn": "2015-07-20T15:49:04",
"valueId": "string",
"type": "DELETED_EVENT"
},
"exampleSetFlag": true
},
"DomainEvent": {
"type": "object",
"properties": {
"id": {
"type": "string",
"exampleSetFlag": false
},
"occuredOn": {
"type": "string",
"format": "date-time",
"exampleSetFlag": false
},
"type": {
"type": "string",
"exampleSetFlag": false
}
},
"example": {
"id": "string",
"occuredOn": "2015-07-20T15:49:04",
"type": "string"
},
"discriminator": {
"propertyName": "type",
"mapping": {
"CHANGED_EVENT": "#/components/schemas/ChangedEvent",
"DELETED_EVENT": "#/components/schemas/DeletedEvent"
}
},
"exampleSetFlag": true,
"oneOf": [
{
"$ref": "#/components/schemas/ChangedEvent",
"exampleSetFlag": false
},
{
"$ref": "#/components/schemas/DeletedEvent",
"exampleSetFlag": false
}
]
}
}
}
package pckg.test;
// import
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"id",
"occuredOn",
"type"
})
@Generated("jsonschema2pojo")
@JsonTypeInfo(property = "type", use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(name = "CHANGED_EVENT", value = ChangedEvent.class),
@JsonSubTypes.Type(name = "DELETED_EVENT", value = DeletedEvent.class)
})
public class DomainEvent implements Serializable
{
@JsonProperty("id")
protected String id;
@JsonProperty("occuredOn")
protected LocalDateTime occuredOn;
@JsonProperty("type")
protected String type;
@JsonIgnore
protected Map<String, Object> additionalProperties = new LinkedHashMap<String, Object>();
protected final static long serialVersionUID = 46991903L;
//getters and setters
}
// import
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"id",
"occuredOn",
"valueId",
"type"
})
@Generated("jsonschema2pojo")
public class DeletedEvent
extends DomainEvent
implements Serializable
{
@JsonProperty("id")
private String id;
@JsonProperty("occuredOn")
private LocalDateTime occuredOn;
@JsonProperty("valueId")
private String valueId;
@JsonProperty("type")
private String type;
@JsonIgnore
private Map<String, Object> additionalProperties = new LinkedHashMap<String, Object>();
private final static long serialVersionUID = 73263837L;
// getters and setters
}
package pckg.test;
//import
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"id",
"occuredOn",
"value",
"type"
})
@Generated("jsonschema2pojo")
public class ChangedEvent
extends DomainEvent
implements Serializable
{
@JsonProperty("id")
private String id;
@JsonProperty("occuredOn")
private LocalDateTime occuredOn;
@JsonProperty("value")
private String value;
@JsonProperty("type")
private String type;
@JsonIgnore
private Map<String, Object> additionalProperties = new LinkedHashMap<String, Object>();
private final static long serialVersionUID = 5446866391322866265L;
//getters and setters
}
plugins {
id 'io.github.stepanovd.springwolf2dto' version '1.0.1-alpha'
}
springWolfDoc2DTO{
url = '//localhost:8080/springwolf/docs'
targetPackage = 'example.package'
documentationTitle = 'my-service'
targetDirectory = project.layout.getBuildDirectory().dir("generated-sources")
}
./gradle -q generateDTO
In this article, I described how you can use the springwolfdocs2dto plugin to generate new DTO classes based on the AsyncApi documentation. At the same time, new classes will be according to original inheritance and contain Jackson annotations for correct deserialization. I hope you find this plugin useful to you.