diff --git a/config/AMBASSADORS_MEMBERS.json b/config/AMBASSADORS_MEMBERS.json index 27fa3a73a60..b4ab927f8d8 100644 --- a/config/AMBASSADORS_MEMBERS.json +++ b/config/AMBASSADORS_MEMBERS.json @@ -55,12 +55,12 @@ "title": "Senior Solution Architect / Head of API Experience and Operations", "github": "danielkocot", "twitter": "dk_1977", - "linkedin": "daniel-kocot", + "linkedin": "danielkocot", "company": "Codecentric AG", "country": "πŸ‡©πŸ‡ͺ", "contributions": [ { - "type": "article", + "type": "article", "title": "AsyncAPI - Documentation of event and message-driven architectures", "date": { "year": 2021, @@ -68,32 +68,32 @@ }, "link": "https://blog.codecentric.de/en/2021/09/asyncapi-documentation-event-message-driven-architectures/?hmsr=joyk.com&utm_source=joyk.com&utm_medium=referral" }, - { - "type": "article", + { + "type": "article", "title": "AsyncAPI specification updates", "date": { "year": 2022, "month": "February" }, - "link": "https://blog.codecentric.de/en/2022/02/asyncapi-version-2-3-0-specification-updates/" + "link": "https://blog.codecentric.de/en/2022/02/asyncapi-version-2-3-0-specification-updates/" }, { "type": "presentation", - "title": "AsyncAPI 101", + "title": "AsyncAPI 101", "date": { "year": 2022, "month": "May" - }, + }, "link": "https://www.buildingiot.de/veranstaltung-13856-0-asyncapi-101.html" }, { "type": "presentation", - "title": "Adopting AsyncAPI in Enterprisey Contexts, AsyncAPI Conference", + "title": "Adopting AsyncAPI in Enterprisey Contexts, AsyncAPI Conference", "date": { "year": 2022, "month": "November" - }, - "link": "https://www.youtube.com/watch?v=_wTcV63jrnU" + }, + "link": "https://www.youtube.com/watch?v=_wTcV63jrnU" } ] }, diff --git a/config/MAINTAINERS.json b/config/MAINTAINERS.json index 98015228c24..b9419b15642 100644 --- a/config/MAINTAINERS.json +++ b/config/MAINTAINERS.json @@ -72,7 +72,7 @@ }, { "name": "Cameron Rushton", - "github": "CameronRushton", + "github": "cameronrushton", "slack": "U01DVKKAV5K", "availableForHire": false, "company": "Solace", @@ -136,7 +136,7 @@ }, { "name": "Gerald Loeffler", - "github": "GeraldLoeffler", + "github": "geraldloeffler", "linkedin": "geraldloeffler", "slack": "U01P5QDLP0X", "availableForHire": false, @@ -167,7 +167,7 @@ }, { "name": "Khuda Dad Nomani", - "github": "KhudaDad414", + "github": "khudadad414", "twitter": "KhudaDadNomani", "linkedin": "khudadadnomani", "slack": "U01RVRD1TCL", @@ -175,10 +175,10 @@ "company": "Postman", "isTscMember": true, "repos": [ - "optimizer", "bindings", "glee", - ".github" + ".github", + "optimizer" ] }, { @@ -266,7 +266,7 @@ { "name": "Azeez Elegbede", "linkedin": "acebuild", - "github": "AceTheCreator", + "github": "acethecreator", "twitter": "_acebuild", "slack": "U01RWDD69PZ", "company": "Postman", @@ -307,7 +307,7 @@ }, { "name": "Nektarios Fifes", - "github": "NektariosFifes", + "github": "nektariosfifes", "linkedin": "nektarios-fifes-372740220", "slack": "U01SE93Q48N", "availableForHire": true, @@ -318,7 +318,7 @@ }, { "name": "Pavel Bodiachevskii", - "github": "Pakisan", + "github": "pakisan", "slack": "U0132LQU8C9", "twitter": "pbodiachevskii", "availableForHire": false, @@ -387,7 +387,7 @@ }, { "name": "Souvik De", - "github": "Souvikns", + "github": "souvikns", "slack": "U01SGCZMJKW", "twitter": "souvik_ns", "linkedin": "souvik-de-a2b941169", @@ -417,7 +417,7 @@ }, { "name": "David Pereira", - "github": "BOLT04", + "github": "bolt04", "twitter": "BOLT2938", "slack": "U02EC8BT0TX", "linkedin": "jos\u00e9-david-pereira-13ba5315a", @@ -442,7 +442,7 @@ }, { "name": "Kieran Murphy", - "github": "KieranM1999", + "github": "kieranm1999", "linkedin": "kieran-murphy-175b0412b", "availableForHire": false, "slack": "U02FT2TKM37", @@ -454,7 +454,7 @@ }, { "name": "Tom Jefferson", - "github": "JEFFLUFC", + "github": "jefflufc", "linkedin": "t-jefferson", "slack": "U02FPPCEH6H", "availableForHire": false, @@ -490,7 +490,7 @@ }, { "name": "Semen Tenishchev", - "github": "Tenischev", + "github": "tenischev", "linkedin": "semen-tenishchev", "availableForHire": true, "slack": "U011D1DAU6S", @@ -501,7 +501,7 @@ }, { "name": "Samridhi Agrawal", - "github": "Samridhi-98", + "github": "samridhi-98", "slack": "U02T2MY9W5T", "linkedin": "samridhi-agrawal-1713201ab", "availableForHire": false, @@ -538,7 +538,7 @@ }, { "name": "Florence Njeri", - "github": "Florence-Njeri", + "github": "florence-njeri", "linkedin": "florencenjeri", "slack": "U03D18YKX2M", "twitter": "njericodes", @@ -578,7 +578,7 @@ }, { "name": "Alexander Wichmann", - "github": "VisualBean", + "github": "visualbean", "linkedin": "alexcarlsen", "slack": "U04C58GB8TF", "availableForHire": false, @@ -592,7 +592,7 @@ "name": "Barba\u00f1o Gonz\u00e1lez", "linkedin": "barbano-gonzalez-moreno", "twitter": "BarbanoGonzalez", - "github": "Barbanio", + "github": "barbanio", "slack": "U01J42QDSLU", "availableForHire": false, "company": "Postman", @@ -616,7 +616,7 @@ }, { "name": "Heiko Henning", - "github": "GreenRover", + "github": "greenrover", "slack": "U03AC4G51H8", "availableForHire": false, "company": "mtrail GmbH", diff --git a/config/meetings.json b/config/meetings.json index 4ff41cecd08..bfc69416cda 100644 --- a/config/meetings.json +++ b/config/meetings.json @@ -1,25 +1,4 @@ [ - { - "title": "Spec 3.0 Meeting", - "calLink": "https://www.google.com/calendar/event?eid=Z2l0am1xbTBhcm8xa2sxc2dyOGdoaXRmZTQgY19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bn", - "url": "https://github.com/asyncapi/community/issues/670", - "banner": "", - "date": "2023-04-26T15:00:00.000Z" - }, - { - "title": "Tackling interoperability challenges in open-source", - "calLink": "https://www.google.com/calendar/event?eid=M2Njb2dib2VvMXF1dmR1bGJ0ZG1tb2txMWsgY19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bn", - "url": "https://github.com/asyncapi/community/issues/681", - "banner": "https://user-images.githubusercontent.com/66913810/233324999-bce12c6f-6e8e-4aca-acf5-053cfa22378a.png", - "date": "2023-04-26T09:00:00.000Z" - }, - { - "title": "Community Meeting", - "calLink": "https://www.google.com/calendar/event?eid=YjcwZ2ZmOXNmMmU1YmRybXB0azk1Y2prb3MgY19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bn", - "url": "https://github.com/asyncapi/community/issues/688", - "banner": "https://user-images.githubusercontent.com/40604284/233369404-38892fbe-7082-4492-8aed-0a97ee8df011.png", - "date": "2023-05-02T08:00:00.000Z" - }, { "title": "AsyncAPI x Google Season of Doc's 2023", "calLink": "https://www.google.com/calendar/event?eid=Yzdyb2F1c3RjOXVvcTh2NXFiZGpyNjgzN3MgY19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bn", @@ -190,17 +169,17 @@ }, { "title": "Spec 3.0 Docs Meeting", - "calLink": "https://www.google.com/calendar/event?eid=aWhzNWwxZWkxdWVlMWhvbGs4aWY2YzU1am8gY19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bn", - "url": "https://github.com/asyncapi/community/issues/791", + "calLink": "https://www.google.com/calendar/event?eid=cG9iOHNqZGlrbmg4cnUxanMzMTgyN3AxdnMgY19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bn", + "url": "https://github.com/asyncapi/community/issues/792", "banner": "", - "date": "2023-08-03T14:30:00.000Z" + "date": "2023-08-17T14:30:00.000Z" }, { "title": "Spec 3.0 Docs Meeting", - "calLink": "https://www.google.com/calendar/event?eid=cG9iOHNqZGlrbmg4cnUxanMzMTgyN3AxdnMgY19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bn", - "url": "https://github.com/asyncapi/community/issues/792", + "calLink": "https://www.google.com/calendar/event?eid=aHJwdnA1bzI1ajVjNzliZ2h2bm1nZnI3b3MgY19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bn", + "url": "https://github.com/asyncapi/community/issues/793", "banner": "", - "date": "2023-08-17T14:30:00.000Z" + "date": "2023-08-31T14:30:00.000Z" }, { "title": "Community Meeting", @@ -216,18 +195,18 @@ "banner": "", "date": "2023-07-18T08:00:00.000Z" }, - { - "title": "Spec 3.0 Meeting", - "calLink": "https://www.google.com/calendar/event?eid=aTBkZHJ2YWczcG1hZWFqbTFlMWowZjZnbzAgY19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bn", - "url": "https://github.com/asyncapi/community/issues/812", - "banner": "", - "date": "2023-08-02T16:00:00.000Z" - }, { "title": "Community Meeting", "calLink": "https://www.google.com/calendar/event?eid=YmlybTZwODdmMzBnNGg0b3J1OWxmdnBxNmMgY19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bn", "url": "https://github.com/asyncapi/community/issues/818", "banner": "https://user-images.githubusercontent.com/40604284/256949583-958c34c8-4256-4ac5-852b-e00ec094fad0.png", "date": "2023-08-08T16:00:00.000Z" + }, + { + "title": "Spec 3.0 Meeting", + "calLink": "https://www.google.com/calendar/event?eid=NG9lc2RwN3A2djFmNmRxaHZoaWRjMnRhaDAgY19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bn", + "url": "https://github.com/asyncapi/community/issues/823", + "banner": "", + "date": "2023-08-02T16:00:00.000Z" } ] \ No newline at end of file diff --git a/config/newsroom_videos.json b/config/newsroom_videos.json index 3eda25f1a2d..8dddcf03788 100644 --- a/config/newsroom_videos.json +++ b/config/newsroom_videos.json @@ -1,4 +1,16 @@ [ + { + "image_url": "https://i.ytimg.com/vi/1tyyLGAicyE/hqdefault.jpg", + "title": "Community Meeting (August 8th 2023)", + "description": "https://github.com/asyncapi/community/issues/818 Lukasz, Animesh, Maya, and others had a brief conversation where they ...", + "videoId": "1tyyLGAicyE" + }, + { + "image_url": "https://i.ytimg.com/vi/WW5sLVHa0b8/hqdefault.jpg", + "title": "Spec 3.0 (Aug 2th 2023)", + "description": "https://github.com/asyncapi/community/issues/823.", + "videoId": "WW5sLVHa0b8" + }, { "image_url": "https://i.ytimg.com/vi/CagY5otdY14/hqdefault.jpg", "title": "Community Meeting (July 25th 2023)", @@ -16,17 +28,5 @@ "title": "Open Standards and Private Products: A DevRel's Dream or Nightmare", "description": "We are discussing the impact of open standards and working with private products as a DevRel.", "videoId": "WlD5rqmfezw" - }, - { - "image_url": "https://i.ytimg.com/vi/5o5BHJqGxIg/hqdefault.jpg", - "title": "Community Meeting (July 11th 2023)", - "description": "https://github.com/asyncapi/community/issues/784.", - "videoId": "5o5BHJqGxIg" - }, - { - "image_url": "https://i.ytimg.com/vi/Lunp8eSLONc/hqdefault.jpg", - "title": "Spec 3.0 DOCS Meeting (July 6th 2023)", - "description": "https://github.com/asyncapi/community/issues/774.", - "videoId": "Lunp8eSLONc" } ] \ No newline at end of file diff --git a/cypress/fixtures/MockData-scripts.js b/cypress/fixtures/MockData-scripts.js new file mode 100644 index 00000000000..44f7f39cef7 --- /dev/null +++ b/cypress/fixtures/MockData-scripts.js @@ -0,0 +1,304 @@ + +//main DocTree mockData and expected tree +export const mockNavItems = [ + { title: 'Welcome', weight: 0, isRootSection: true, isSection: true, rootSectionId: 'welcome', sectionWeight: 0, slug: '/docs' }, + { title: 'Introduction', weight: 1, isRootSection: true, isSection: true, rootSectionId: 'introduction', sectionWeight: 1, slug: '/docs/introduction' }, + { title: 'Overview', weight: 0, isSection: false, rootSectionId: 'introduction', slug: '/docs/introduction/overview' }, + { title: 'Getting Started', weight: 1, isSection: false, rootSectionId: 'introduction', slug: '/docs/introduction/getting-started' }, + { title: 'Reference', weight: 2, isRootSection: true, isSection: true, rootSectionId: 'reference', sectionWeight: 2, slug: '/docs/reference' }, + { title: 'Specification', weight: 0, isSection: true, parent: 'reference', sectionId: 'specification', slug: '/docs/reference/specification' }, + { title: 'Version 1.0', weight: 0, isSection: false, rootSectionId: 'reference', sectionId: 'specification', slug: '/docs/reference/specification/1.0' }, + { title: 'Version 2.0 (prerelease)', weight: 1, isSection: false, rootSectionId: 'reference', sectionId: 'specification', slug: '/docs/reference/specification/2.0', isPrerelease: true } +] + +export const expectedTree = { + "introduction": { + "children": { + "Getting Started": { + "item": { + "isSection": false, + "rootSectionId": "introduction", + "slug": "/docs/introduction/getting-started", + "title": "Getting Started", + "weight": 1 + } + }, + "Overview": { + "item": { + "isSection": false, + "rootSectionId": "introduction", + "slug": "/docs/introduction/overview", + "title": "Overview", + "weight": 0 + } + } + }, + "item": { + "isRootSection": true, + "isSection": true, + "rootSectionId": "introduction", + "sectionWeight": 1, + "slug": "/docs/introduction", + "title": "Introduction", + "weight": 1 + } + }, + "reference": { + "children": { + "specification": { + "children": [ + { + "isPrerelease": true, + "isSection": false, + "rootSectionId": "reference", + "sectionId": "specification", + "slug": "/docs/reference/specification/2.0", + "title": "Version 2.0 (prerelease)", + "weight": 1 + } + ], + "item": { + "isSection": true, + "parent": "reference", + "sectionId": "specification", + "slug": "/docs/reference/specification", + "title": "Specification", + "weight": 0 + } + } + }, + "item": { + "isRootSection": true, + "isSection": true, + "rootSectionId": "reference", + "sectionWeight": 2, + "slug": "/docs/reference", + "title": "Reference", + "weight": 2 + } + }, + "welcome": { + "children": { + }, + "item": { + "isRootSection": true, + "isSection": true, + "rootSectionId": "welcome", + "sectionWeight": 0, + "slug": "/docs", + "title": "Welcome", + "weight": 0 + } + } +} + +//main mockDocObjec and //expected +export const mockDocObject = { + item: { title: 'Welcome', weight: 0, isRootSection: true, isSection: true, rootSectionId: 'welcome', sectionWeight: 0, slug: '/docs' }, + children: { + 'Introduction': { + item: { title: 'Introduction', weight: 1, isRootSection: true, isSection: true, rootSectionId: 'introduction', sectionWeight: 1, slug: '/docs/introduction' }, + children: { + 'Overview': { item: { title: 'Overview', weight: 0, isSection: false, rootSectionId: 'introduction', slug: '/docs/introduction/overview' } }, + 'Getting Started': { item: { title: 'Getting Started', weight: 1, isSection: false, rootSectionId: 'introduction', slug: '/docs/introduction/getting-started' } } + } + } + } +} +// expected output for convertDocPosts +export const expectedDocsArray = [ + { title: 'Welcome', weight: 0, isRootSection: true, isSection: true, rootSectionId: 'welcome', sectionWeight: 0, slug: '/docs' }, + { title: 'Introduction', weight: 1, isRootSection: true, isSection: true, rootSectionId: 'introduction', sectionWeight: 1, slug: '/docs/introduction' }, + { title: 'Overview', weight: 0, isSection: false, rootSectionId: 'introduction', slug: '/docs/introduction/overview' }, + { title: 'Getting Started', weight: 1, isSection: false, rootSectionId: 'introduction', slug: '/docs/introduction/getting-started' } +] + +export const mockDocPosts = [ + { title: 'Welcome', weight: 0, isRootSection: true, isSection: true, rootSectionId: 'welcome', sectionWeight: 0, slug: '/docs', content: 'Welcome to the docs' }, + { title: 'Overview', weight: 0, isSection: false, rootSectionId: 'introduction', slug: '/docs/introduction/overview', content: 'This is an overview of the docs' }, + { title: 'Getting Started', weight: 1, isSection: false, rootSectionId: 'introduction', slug: '/docs/introduction/getting-started', content: 'This is how to get started with the docs' }, + { title: 'Version 1.0', weight: 0, isSection: false, rootSectionId: 'reference', sectionId: 'specification', slug: '/docs/reference/specification/1.0', content: 'This is the specification for version 1.0' }, + { title: 'Version 2.0 (prerelease)', weight: 1, isSection: false, rootSectionId: 'reference', sectionId: 'specification', slug: '/docs/reference/specification/2.0', content: 'This is the specification for version 2.0 (prerelease)', isPrerelease: true } +] + +// mock data for treePosts +export const mockTreePosts = { + welcome: { + item: { title: 'Welcome', weight: 0, isRootSection: true, isSection: true, rootSectionId: 'welcome', sectionWeight: 0, slug: '/docs' }, + children: {} + }, + introduction: { + item: { title: 'Introduction', weight: 1, isRootSection: true, isSection: true, rootSectionId: 'introduction', sectionWeight: 1, slug: '/docs/introduction' }, + children: { + Overview: { item: { title: 'Overview', weight: 0, isSection: false, rootSectionId: 'introduction', slug: '/docs/introduction/overview' } }, + 'Getting Started': { item: { title: 'Getting Started', weight: 1, isSection: false, rootSectionId: 'introduction', slug: '/docs/introduction/getting-started' } } + } + }, + reference: { + item: { title: 'Reference', weight: 2, isRootSection: true, isSection: true, rootSectionId: 'reference', sectionWeight: 2, slug: '/docs/reference' }, + children: { + specification: { + item: { title: 'Specification', weight: 0, isSection: true, parent: 'reference', sectionId: 'specification', slug: '/docs/reference/specification/2.0' }, + children: [ + { title: 'Version 1.0', weight: 0, isSection: false, rootSectionId: 'reference', sectionId: 'specification', slug: '/docs/reference/specification/1.0' }, + { + title: 'Version 2.0 (prerelease)', weight: 1, isSection: false, rootSectionId: 'reference', sectionId: 'specification', slug: '/docs/reference/specification/2.0', + isPrerelease: true + } + ] + } + } + } +} + +// expected output for addDocButtons +export const expectedStructuredPosts = [ + { + "content": "Welcome to the docs", + "isRootSection": true, + "isSection": true, + "rootSectionId": "welcome", + "sectionWeight": 0, + "slug": "/docs", + "title": "Welcome", + "weight": 0 + }, + { + "isRootSection": true, + "isSection": true, + "rootSectionId": "introduction", + "sectionWeight": 1, + "slug": "/docs/introduction", + "title": "Introduction", + "weight": 1 + }, + { + "isSection": false, + "nextPage": { + "href": "/docs/introduction/getting-started", + "title": "Getting Started" + }, + "prevPage": { + "href": "/docs", + "title": "Welcome - Welcome" + }, + "rootSectionId": "introduction", + "slug": "/docs/introduction/overview", + "title": "Overview", + "weight": 0 + }, + { + "isSection": false, + "nextPage": { + "href": "/docs/reference/specification/2.0", + "title": "Reference - Specification" + }, + "prevPage": { + "href": "/docs/introduction/overview", + "title": "Overview" + }, + "rootSectionId": "introduction", + "slug": "/docs/introduction/getting-started", + "title": "Getting Started", + "weight": 1 + }, + { + "isRootSection": true, + "isSection": true, + "rootSectionId": "reference", + "sectionWeight": 2, + "slug": "/docs/reference", + "title": "Reference", + "weight": 2 + }, + { + "isSection": true, + "parent": "reference", + "sectionId": "specification", + "slug": "/docs/reference/specification/2.0", + "title": "Specification", + "weight": 0 + }, + { + "isSection": false, + "nextPage": { + "href": "/docs/reference/specification/2.0", + "title": "Version 2.0 (prerelease)" + }, + "prevPage": { + "href": "/docs/reference", + "title": "Reference - Reference" + }, + "rootSectionId": "reference", + "sectionId": "specification", + "slug": "/docs/reference/specification/1.0", + "title": "Version 1.0", + "weight": 0 + }, + { + "isPrerelease": true, + "isSection": false, + "prevPage": { + "href": "/docs/reference/specification/1.0", + "title": "Version 1.0" + }, + "rootSectionId": "reference", + "sectionId": "specification", + "slug": "/docs/reference/specification/2.0", + "title": "Version 2.0 (prerelease)", + "weight": 1 + } +] + +// create a new mock data with only one root section and no subsections +export const mockDocPostsOneRoot = [ + { title: 'Welcome', weight: 0, isRootSection: true, isSection: true, rootSectionId: 'welcome', sectionWeight: 0, slug: '/docs', content: 'Welcome to the docs' }, + { title: 'Overview', weight: 0, isSection: false, rootSectionId: 'welcome', slug: '/docs/overview', content: 'This is an overview of the docs' }, + { title: 'Getting Started', weight: 1, isSection: false, rootSectionId: 'welcome', slug: '/docs/getting-started', content: 'This is how to get started with the docs' } +] + +export const mockTreePostsOneRoot = { + welcome: { + item: { title: 'Welcome', weight: 0, isRootSection: true, isSection: true, rootSectionId: 'welcome', sectionWeight: 0, slug: '/docs' }, + children: { + Overview: { item: { title: 'Overview', weight: 0, isSection: false, rootSectionId: 'welcome', slug: '/docs/overview' } }, + 'Getting Started': { item: { title: 'Getting Started', weight: 1, isSection: false, rootSectionId: 'welcome', slug: '/docs/getting-started' } } + } + } +} + +// create a new expected output with only one root section and no subsections +export const expectedStructuredPostsOneRoot = [ + { + "content": "Welcome to the docs", + "isRootSection": true, + "isSection": true, + "rootSectionId": "welcome", + "sectionWeight": 0, + "slug": "/docs", + "title": "Welcome", + "weight": 0 + }, + { + "isSection": false, + "nextPage": { + "href": "/docs/getting-started", + "title": "Getting Started" + }, + "rootSectionId": "welcome", + "slug": "/docs/overview", + "title": "Overview", + "weight": 0 + }, + { + "isSection": false, + "prevPage": { + "href": "/docs/overview", + "title": "Overview" + }, + "rootSectionId": "welcome", + "slug": "/docs/getting-started", + "title": "Getting Started", + "weight": 1 + } +] diff --git a/cypress/test/scripts/build-docs.cy.js b/cypress/test/scripts/build-docs.cy.js new file mode 100644 index 00000000000..453d24d2f65 --- /dev/null +++ b/cypress/test/scripts/build-docs.cy.js @@ -0,0 +1,46 @@ +import { buildNavTree } from '../../../scripts/build-docs'; +import { convertDocPosts } from '../../../scripts/build-docs'; +import { addDocButtons } from '../../../scripts/build-docs'; +import { mockNavItems, expectedTree, mockDocObject, expectedDocsArray, mockDocPosts, mockTreePosts, expectedStructuredPosts, mockTreePostsOneRoot, mockDocPostsOneRoot, expectedStructuredPostsOneRoot } from '../../fixtures/MockData-scripts'; + + +describe('buildNavTree function', () => { + it('should return a tree object with the correct structure and order of nav items', () => { + // call the function with the mock data + const actualTree = buildNavTree(mockNavItems); + expect(actualTree).to.deep.equal(expectedTree); + }); + +}); + +describe('convertDocPosts function', () => { + it('should return an array of doc posts with the correct order and properties', () => { + // call the function with the mock data + const actualDocsArray = convertDocPosts(mockDocObject); + // assert that the actual array matches the expected array + expect(actualDocsArray).to.deep.equal(expectedDocsArray); + }); +}); + +describe('addDocButtons function', () => { + + it('should return an array of structured posts with the correct next and prev page buttons', () => { + + // call the function with the mock data + const actualStructuredPosts = addDocButtons(mockDocPosts, mockTreePosts); + // assert that the actual array matches the expected array + expect(actualStructuredPosts).to.deep.equal(expectedStructuredPosts); + + }); + + // a second test case for when there is only one root section and no subsections + it('should return an array of structured posts with the correct next and prev page buttons when there is only one root section and no subsections', () => { + + // call the function with the new mock data + const actualStructuredPostsOneRoot = addDocButtons(mockDocPostsOneRoot, mockTreePostsOneRoot); + + // assert that the actual array matches the expected array + expect(actualStructuredPostsOneRoot).to.deep.equal(expectedStructuredPostsOneRoot); + + }); +}); \ No newline at end of file diff --git a/dashboard.json b/dashboard.json index 0f03b47ab6c..06bfdbabcc2 100644 --- a/dashboard.json +++ b/dashboard.json @@ -14,27 +14,18 @@ "color": "C2E0C6" } ], - "score": 53.988822684860644 + "score": 26.021935183079695 }, { - "id": "I_kwDOFLhIt84-OUI3", + "id": "I_kwDOBGu-185CELGB", "isPR": false, "isAssigned": false, - "title": "Create educational & technical video explaining AsyncAPI's main features", - "author": "alequetzalli", - "resourcePath": "/asyncapi/community/issues/155", - "repo": "asyncapi/community", - "labels": [ - { - "name": "enhancement", - "color": "a2eeef" - }, - { - "name": "stale", - "color": "ededed" - } - ], - "score": 32.45072852866624 + "title": "Work on 3.0 release", + "author": "jonaslagoni", + "resourcePath": "/asyncapi/spec/issues/691", + "repo": "asyncapi/spec", + "labels": [], + "score": 22.9739670999407 }, { "id": "MDU6SXNzdWU5OTMxODc5ODM=", @@ -53,47 +44,35 @@ "score": 22.68679251119144 }, { - "id": "I_kwDOBW5R_c5Pi3rO", + "id": "I_kwDOBW5R_c5J6qNe", "isPR": false, "isAssigned": false, - "title": "Epic roadmap to the new AsyncAPI community section", - "author": "AceTheCreator", - "resourcePath": "/asyncapi/website/issues/903", + "title": "Measuring AsyncAPI spec adoption", + "author": "derberg", + "resourcePath": "/asyncapi/website/issues/780", "repo": "asyncapi/website", "labels": [ { - "name": "🎨 design", - "color": "0D67D3" - } - ], - "score": 22.399617922442182 - }, - { - "id": "I_kwDOBGu-184_rP6l", - "isPR": false, - "isAssigned": false, - "title": "Let channels be identified by an ID rather than their address.", - "author": "smoya", - "resourcePath": "/asyncapi/spec/issues/663", - "repo": "asyncapi/spec", - "labels": [ + "name": "enhancement", + "color": "84b6eb" + }, { - "name": "πŸ’­ Strawman (RFC 0)", - "color": "C2E0C6" + "name": "stale", + "color": "ededed" } ], - "score": 21.538094156194408 + "score": 19.240697446200336 }, { - "id": "PR_kwDOBW5R_c5TUuUu", + "id": "PR_kwDOFLhIt85Vmgtj", "isPR": true, "isAssigned": false, - "title": "chore(blog): adding 3 New Articles as Blog Posts", - "author": "ivangsa", - "resourcePath": "/asyncapi/website/pull/1805", - "repo": "asyncapi/website", + "title": "feat: youtube to anchor workflow", + "author": "AnimeshKumar923", + "resourcePath": "/asyncapi/community/pull/805", + "repo": "asyncapi/community", "labels": [], - "score": 20.67657038994663 + "score": 16.65612614745701 }, { "id": "PR_kwDOBW5R_c5VAjCE", @@ -136,16 +115,68 @@ "score": 15.794602381209232 }, { - "id": "PR_kwDOFLhIt85Vmgtj", - "isPR": true, + "id": "I_kwDOFLhIt84-OUI3", + "isPR": false, "isAssigned": false, - "title": "feat: youtube to anchor workflow", - "author": "AnimeshKumar923", - "resourcePath": "/asyncapi/community/pull/805", + "title": "Create educational & technical video explaining AsyncAPI's main features", + "author": "alequetzalli", + "resourcePath": "/asyncapi/community/issues/155", "repo": "asyncapi/community", + "labels": [ + { + "name": "enhancement", + "color": "a2eeef" + }, + { + "name": "stale", + "color": "ededed" + } + ], + "score": 15.64084401961705 + }, + { + "id": "PR_kwDODyzcIc5NDHYS", + "isPR": true, + "isAssigned": false, + "title": "ci: new github action which logs all the reactions in prs and issues", + "author": "14Richa", + "resourcePath": "/asyncapi/.github/pull/220", + "repo": "asyncapi/.github", + "labels": [ + { + "name": "stale", + "color": "ededed" + } + ], + "score": 15.220253203710714 + }, + { + "id": "I_kwDOCVQpZM5M_dcV", + "isPR": false, + "isAssigned": true, + "title": "DocsUI: Messages Object output", + "author": "mcturco", + "resourcePath": "/asyncapi/asyncapi-react/issues/618", + "repo": "asyncapi/asyncapi-react", "labels": [], "score": 14.645904026212197 }, + { + "id": "I_kwDOBW5R_c5BIl5P", + "isPR": false, + "isAssigned": true, + "title": "Add new page for collecting user testing participants", + "author": "mcturco", + "resourcePath": "/asyncapi/website/issues/529", + "repo": "asyncapi/website", + "labels": [ + { + "name": "enhancement", + "color": "84b6eb" + } + ], + "score": 14.645904026212197 + }, { "id": "PR_kwDOFDnrNc5RUbi_", "isPR": true, @@ -156,41 +187,39 @@ "repo": "asyncapi/cli", "labels": [], "score": 14.645904026212197 - }, + } + ], + "goodFirstIssues": [ { - "id": "MDU6SXNzdWUxMjMwODQwMDM4", - "isPR": false, + "id": "I_kwDOBGu-185t3Fe8", + "title": "Add logo of bronze sponsors to readme", "isAssigned": false, - "title": "Usages of allOf within message payload could be flattened", - "author": "jamescrowley", - "resourcePath": "/asyncapi/asyncapi-react/issues/596", - "repo": "asyncapi/asyncapi-react", + "resourcePath": "/asyncapi/spec/issues/961", + "repo": "asyncapi/spec", + "author": "derberg", + "area": "Unknown", "labels": [ { - "name": "stale", - "color": "ededed" + "name": "πŸ“‘ docs", + "color": "E50E99" } - ], - "score": 14.07155484871368 + ] }, { - "id": "PR_kwDOFLhIt85TFWOL", - "isPR": true, + "id": "I_kwDOFDnrNc5tcrDS", + "title": "Add a new flag to set context when it is created ", "isAssigned": false, - "title": "ci: verify changes to Maintainers.yaml made by the bot", - "author": "14Richa", - "resourcePath": "/asyncapi/community/pull/750", - "repo": "asyncapi/community", + "resourcePath": "/asyncapi/cli/issues/726", + "repo": "asyncapi/cli", + "author": "Souvikns", + "area": "Unknown", "labels": [ { - "name": "do-not-merge", - "color": "B60205" + "name": "enhancement", + "color": "a2eeef" } - ], - "score": 13.497205671215161 - } - ], - "goodFirstIssues": [ + ] + }, { "id": "I_kwDOBW5R_c5sqLtN", "title": "[πŸ“‘ Docs]: import Glee docs under tools folder", @@ -255,21 +284,6 @@ } ] }, - { - "id": "I_kwDOFi_gUM5rHSAR", - "title": "Show server url", - "isAssigned": false, - "resourcePath": "/asyncapi/glee/issues/475", - "repo": "asyncapi/glee", - "author": "KhudaDad414", - "area": "typescript", - "labels": [ - { - "name": "enhancement", - "color": "a2eeef" - } - ] - }, { "id": "I_kwDOBGu-185qGt6A", "title": "Ensure consistency when using either `Application` or `API` terms", @@ -569,25 +583,6 @@ } ] }, - { - "id": "I_kwDOE8Qh385m6JkV", - "title": "Add runtime testing for C++", - "isAssigned": false, - "resourcePath": "/asyncapi/modelina/issues/1335", - "repo": "asyncapi/modelina", - "author": "jonaslagoni", - "area": "Unknown", - "labels": [ - { - "name": "enhancement", - "color": "a2eeef" - }, - { - "name": "C++ generator", - "color": "1573BB" - } - ] - }, { "id": "I_kwDOE8Qh385m6IlQ", "title": "Add runtime testing for Kotlin", @@ -755,13 +750,13 @@ ] }, { - "id": "I_kwDOFi_gUM5hAZlI", - "title": "Better logging for MQTT adapter", + "id": "I_kwDOB5hCo85hBDME", + "title": "Enable reusability of schemas between versions", "isAssigned": false, - "resourcePath": "/asyncapi/glee/issues/406", - "repo": "asyncapi/glee", - "author": "KhudaDad414", - "area": "typescript", + "resourcePath": "/asyncapi/spec-json-schemas/issues/364", + "repo": "asyncapi/spec-json-schemas", + "author": "jonaslagoni", + "area": "javascript", "labels": [ { "name": "enhancement", @@ -956,6 +951,10 @@ "author": "jonaslagoni", "area": "docs", "labels": [ + { + "name": "stale", + "color": "819cd3" + }, { "name": "Hacktoberfest", "color": "FF8AE2" @@ -1066,6 +1065,10 @@ "name": "enhancement", "color": "a2eeef" }, + { + "name": "stale", + "color": "ededed" + }, { "name": "Hacktoberfest", "color": "FF8AE2" diff --git a/pages/blog/api-first-with-asyncapi.md b/pages/blog/api-first-with-asyncapi.md new file mode 100644 index 00000000000..4a053553454 --- /dev/null +++ b/pages/blog/api-first-with-asyncapi.md @@ -0,0 +1,284 @@ +--- +title: API-First with AsyncAPI +date: 2023-08-10T06:00:00+01:00 +type: Engineering +tags: + - API-First + - EDA + - Specification +cover: /img/posts/zenwave/api-first-with-asyncapi-banner.png +canonical: https://zenwave360.github.io/Event-Driven-Architectures/API-First-with-AsyncAPI +authors: + - name: Ivan Garcia Sainz-Aja + photo: /img/avatars/ivangsa.webp + link: https://www.linkedin.com/in/ivangarciasainzaja/ + byline: Software Architect at Sngular. Speaker. Building ZenWave 360ΒΊ +excerpt: 'If you are familiar with OpenAPI and OpenAPI Generator API-First process, doing API-First with AsyncAPI is similar.' +--- + +# API-First with AsyncAPI + +- [API-First with AsyncAPI](#api-first-with-asyncapi) + - [Broker-based APIs are Symmetric](#broker-based-apis-are-symmetric) + - [Events, Commands, and Messages](#events-commands-and-messages) + - [Understanding AsyncAPI Definition](#understanding-asyncapi-definition) + - [Info](#info) + - [Servers](#servers) + - [Channels: Publish / Subscribe](#channels-publish--subscribe) + - [Messages](#messages) + - [Message Payloads / Schemas](#message-payloads--schemas) + - [Reusing Configurations: Operation Traits, Message Traits...](#reusing-configurations-operation-traits-message-traits) + - [Different Styles of Event Messages](#different-styles-of-event-messages) + - [Notification Messages](#notification-messages) + - [State Transfer Messages](#state-transfer-messages) + - [Domain Event Messages](#domain-event-messages) + - [Next: Java Code Generator for AsyncAPI](#next-java-code-generator-for-asyncapi) + +If you are familiar with OpenAPI and OpenAPI Generator API-First workflow: + +- First, write the OpenAPI definition, collaborating between API providers and API consumers. +- Then, use OpenAPI Generator, either the maven plugin or a CLI, to generate some DTOs and interfaces from your OpenAPI definition. +- Implementing the generated interfaces, you can create a service for the API. +- As a client, you can use generated interfaces to consume the API with some HTTP client generated behind the scenes. + +When doing API-First with AsyncAPI, the process is similar. After you generate some interfaces and DTOs from your API definition, you use the generated interfaces to produce messages, send them to the broker, and implement them to consume messages from the broker. + +There is still a fundamental difference between OpenAPI and AsyncAPI: OpenAPI is used to document Request-Response / Client-Server APIs, while AsyncAPI is used to document Event-Driven APIs which, except for WebSockets, are Broker-based. + +And broker-based APIs, unlike Client-Server, are inherently **symmetric**. + +## Broker-based APIs are Symmetric + +![Client-server vs broker-based EDAs](/img/posts/zenwave/client-server-vs-broker-eda.excalidraw.svg) + +Because APIs mediated by a broker are inherent **symmetric**, it's difficult to establish the roles of the client/server: what represents a `publish` operation from one side will be a `subscribe` operation seen from the other side. Also, a given service can act as a publisher and subscriber on the same API. + +For these reasons, to avoid defining the same API operations multiple times from each perspective, we propose to define the API only once from the perspective of the provider of the functionality, which may be a producer, a consumer, or both. + +Some definitions: + +- SERVICE: An independent piece of software, typically a microservice, that provides a set of capabilities to other services. +- PROVIDER: The service that implements the functionality of the API. It may be accepting asynchronous command requests or publishing business domain events. +- CLIENT/s: The service/s that uses the API's functionality. It may be requesting asynchronous commands or subscribing to business domain events. +- PRODUCER: A service that writes a given message. +- CONSUMER: A service that reads a given message. + +> Define your AsyncAPI from the perspective of the **PROVIDER** of the functionality, which may be a producer, a consumer, or both. Share this definition with your **CLIENTS**. + +Use the table to understand which section of AsyncAPI (publish or subscribe) to use for each topic and which role (provider or client) to use on the plugin configuration. + +| | Events | Commands | +|------------------------------|-----------------------|-------------------------| +| Provider | Produces (publish) | Consumes (subscribe) | +| Client | Consumes (subscribe) | Produces (publish) | +| OperationId Suggested Prefix | **on**<Event Name> | **do**<Command Name> | + +If you still find it confusing which one is a provider and a client, just use this rule: it can be only one provider of a given message, while clients of a given message there can be many: + +- If the provider is the producer, use publish section +- If it is the consumer, use subscribe section. + +## Events, Commands, and Messages + +There are two types of messages in a messaging system: events and commands. An event message describes a change that has already happened, while a command message describes an operation that needs to be carried out. In other words, events are used to notify subscribers about something that has already occurred, while commands are used to initiate an action or process. + +- **Event:** A message describing a change that has already happened. +- **Command:** A message describing an operation that has to be carried out. + +Also, while there can be only one provider that produces a given event, commands can be issued for one or many client producers. + +## Understanding AsyncAPI Definition + +While OpenAPI and AsyncAPI come to document completely different architectural styles, they are similar in many aspects; in fact, AsyncAPI YAML format was initially based on OpenAPI format and structure. + +If you are familiar with OpenAPI, you may find useful the following image borrowed from AsyncAPI documentation (click image to follow): + +[![OpenAPI and AsyncAPI](/img/posts/zenwave/openapi-asyncapi.png)](https://www.asyncapi.com/docs/tutorials/getting-started/coming-from-openapi) + +### Info + +Document your API: name, purpose, contact details, and license... + +### Servers + +Document where your API will be deployed and required security... + +You can also document some server **protocol-specific configurations** using free-form **bindings** property + +### Channels: Publish / Subscribe + +Each channel represents one single broker topic, channel, or queue... where you are about to publish or subscribe. + +Use the table above to understand which section, publish or subscribe, you may want to use. + +In a nutshell: + +> Providers publish events and subscribe to commands/queries/requests. + +If you still find it confusing which is a provider and a client, use this rule: In a given messaging scenario, there can be only one provider of a message, while there can be multiple clients. If the provider is producing messages, use the `publish` section. If the provider is consuming messages, use the `subscribe` section. + +### Messages + +Use Messages to describe **Headers**, **Payload Schema**, and **Content-Type**. You can also include examples, descriptions, and protocol-specific binding documentation... + +```yml +components: + messages: + turnOnOff: + name: turnOnOff + title: Turn on/off + summary: Command a particular streetlight to turn the lights on or off. + headers: + type: object + properties: + my-app-header: + type: string + payload: + $ref: "#/components/schemas/turnOnOffPayload" + +``` + +### Message Payloads / Schemas + +You can define message payloads as: + +- Inline components/schemas in the same familiar way you do in OpenAPI +- External files: both `json-schema` and `avro schemas` (.avsc) are supported + +```yml +components: + messages: + MessageWithAsyncAPISchema: + payload: + $ref: "#/components/schemas/turnOnOffPayload" ## asyncapi/inline schema + MessageWithExternalJsonSchema: + schemaFormat: 'application/schema+json;version=draft-07' + payload: + $ref: "some/external/file.schema" ## a json-schema file + MessageWithAvroSchema: + schemaFormat: application/vnd.apache.avro+json;version=1.9.0 + payload: + $ref: "v1/imports/file.avsc" ## and avro schema file +``` + + +### Reusing Configurations: Operation Traits, Message Traits... + +Operation Traits, Message Traits are an excellent way to reuse chunks of configuration between different operations or messages. + +For instance, if various messages share some common headers, you can configure them as Message Traits: +```yml +components: + messages: + CustomerEventMessage: + name: CustomerEventMessage + title: Async Event for a Customer + summary: Async Event for a Customer + schemaFormat: application/vnd.aai.asyncapi;version=2.4.0 + traits: + - $ref: '#/components/messageTraits/CommonHeaders' # 'CommonHeaders' contents will replace 'traits' property + payload: + $ref: '#/components/schemas/CustomerEventPayload' + + messageTraits: + CommonHeaders: + headers: + type: object + properties: + my-app-header: + type: integer + minimum: 0 + maximum: 100 +``` + +And the same concept applies to Operation Traits. + + +## Different Styles of Event Messages + +### Notification Messages + +An Event Notification **contains minimal information about the event** and enough information for interested consumers to locate additional details. The specifics of what information is included in an event notification can vary depending on the system or use case. + +```json +{ + "headers": { + "event-type": "customer-created", + "event-id": "", + "aggregate-id": "1", + "aggregate-type": "customer" + }, + "payload": { + "id": 1, + "eventType": "created", + "link": "/customers/1" + } +} +``` + +### State Transfer Messages + +On the other hand, a State Transfer message **contains the entire state of the aggregate**, so a consumer does not need to make additional calls. This can be useful in situations where subscribers need to maintain a synchronized view of the data. Compacted keyed topics typically use this style of messages. + +```json +{ + "headers": { + "event-id": "", + "aggregate-id": "1", + "aggregate-type": "customer" + }, + "payload": { + "id": 1, + "firstName": "string", + "lastName": "string", + "password": "string", + "email": "string", + "username": "string", + "address": { + "id": 1, + "street": "string", + "city": "string", + "state": "string", + "zip": "string" + } + } +} +``` + + +### Domain Event Messages + +Domain Event Messages **contains information about the event and interesting portions of the underlying aggregate**, but not the entire state of the aggregate. This style of events is typically used for Event Sourcing integration patterns. + +```json +{ + "headers": { + "event-type": "customer-address-updated", + "event-id": "", + "aggregate-id": "1", + "aggregate-type": "customer" + }, + "payload": { + "id": 1, + "eventType": "address-updated", + "customer": { + "id": 1, + "new-address": { + "street": "string", + "city": "string", + "state": "string", + "zip": "string" + } + } + } +} +``` + + +## Next: Java Code Generator for AsyncAPI + +[Next: Java Code Generator for AsyncAPI](zenwave-asyncapi-code-generator.md) + +--- + +Originally published at [https://zenwave360.github.io](https://zenwave360.github.io/Event-Driven-Architectures/API-First-with-AsyncAPI) diff --git a/pages/blog/jhipster-jdl-to-asyncapi.md b/pages/blog/jhipster-jdl-to-asyncapi.md new file mode 100644 index 00000000000..4eb6ef29e1e --- /dev/null +++ b/pages/blog/jhipster-jdl-to-asyncapi.md @@ -0,0 +1,106 @@ +--- +title: Convert JHipster JDL to AsyncAPI v2/v3 with ZenWave SDK +date: 2023-08-10T06:00:00+01:00 +type: Engineering +tags: + - ZenWave SDK + - Code Generator + - JHipster + - DSL +cover: /img/posts/zenwave/jhipster-to-asyncapi-banner.png +canonical: https://zenwave360.github.io/Domain-Driven-Design/JDL-Domain-Language/JHipster-As-IDL-for-AsyncAPIv2 +authors: + - name: Ivan Garcia Sainz-Aja + photo: /img/avatars/ivangsa.webp + link: https://www.linkedin.com/in/ivangarciasainzaja/ + byline: Software Architect at Sngular. Speaker. Building ZenWave 360ΒΊ +excerpt: 'Because writing YAML by hand is now fun... You can generate AsyncAPI v2/v3 from JHipster JDL models with ZenWave SDK.' +--- + +# Generating AsyncAPI definition files from JDL with ZenWaveSDK + +Writing YAML by hand is no fun, but you can simplify the process of writing AsyncAPI definition files by using a Domain Specific Language (DSL). + +[JHipster Domain Language (JDL)](https://www.jhipster.tech/jdl/intro) is a Domain Specific Language (DSL) used to define the domain model of a web application. With JDL, you can describe the entities, relationships, and constraints of your system in a concise and readable way. + +[Zenwave SDK](https://zenwave360.github.io/zenwave-sdk/) is a set of tools to generate (and reverse engineering) code from JDL and API-First models like AsyncAPI and OpenAPI. + +Thanks to ZenWave SDK, you can convert JDL models into AsyncAPI definition files. This can save time and effort in the development process while ensuring that your APIs follow best practices and standards. + +## JDL Example + +```jdl +@aggregate +entity Customer { + username String required minlength(3) maxlength(250) + password String required minlength(3) maxlength(250) + email String required minlength(3) maxlength(250) + firstName String required minlength(3) maxlength(250) + lastName String required minlength(3) maxlength(250) +} +entity Address { + street String + city String + country String + zipCode String +} + +relationship OneToOne { + Customer{address} to Address{customer} +} +``` + + +## Generating AsyncAPI definition files from JDL with ZenWaveSDK + +> See [JDL To AsyncAPI Generator](https://zenwave360.github.io/zenwave-sdk/plugins/jdl-to-asyncapi/) for a complete list of options and [GitHub repository](https://github.com/zenwave360/zenwave-sdk) for install instructions. + +Because JDL can only describe static aspects of your models and doesn't cover dynamic behavior, ZenWave SDK can only infer CRUD operations from your entities, generating: + +- One channel for each entity for both publishing Domain Events and subscribing to Commands/Requests. +- Messages and payloads for each entity Create/Update/Delete events (AVRO and AsyncAPI schema) + +```shell +jbang zw -p io.zenwave360.sdk.plugins.JDLToAsyncAPIPlugin \ + includeCommands=false \ + specFile=src/main/resources/model/entities-model.jdl \ + idType=integer \ + idTypeFormat=int64 \ + annotations=aggregate \ + payloadStyle=event \ + targetFile=src/main/resources/model/asyncapi.yml +``` + +You can choose to generate only Events or Commands using `includeEvents` (default: true) and `includeCommands` (default: false) to filter which channels you want to include in your AsyncAPI definition file. + +You can also filter which entities you want to include Messages for in your AsyncAPI definition file using: `entities`, `skipEntities`, `annotations`, and `skipForAnnotations`. + +**UPDATE:** + +ZenWave SDK version 1.0.6 now supports generating AsyncAPI v3 format. Use `asyncapiVersion=v3` as: + +```shell +jbang zw -p io.zenwave360.sdk.plugins.JDLToAsyncAPIPlugin \ + includeCommands=false \ + specFile=src/main/resources/model/entities-model.jdl \ + idType=integer \ + idTypeFormat=int64 \ + annotations=aggregate \ + payloadStyle=event \ + asyncapiVersion=v3 \ + targetFile=src/main/resources/model/asyncapi.yml +``` + +## Supported Schema Formats and Message Styles + +You can generate AsyncAPI definition files with the following options: + +- Supported Schema Formats: AVRO and AsyncAPI schema +- Supported [Payload Styles](https://zenwave360.github.io/Event-Driven-Architectures/API-First-with-AsyncAPI#different-styles-of-message-payloads): "Entity State Transfer" and "Domain Event" (for Create/Update/Delete events): + - State Transfer message contains the entire state of the aggregate, so a consumer does not need to make additional calls. + - Domain Event Messages contain information about the event and interesting portions of the underlying aggregate, but not the entire state of the aggregate. + +By using JDL to define your domain model and ZenWave SDK to convert it into an AsyncAPI definition file, you can simplify the process of designing and documenting your APIs. This can improve your APIs' overall quality and consistency while reducing errors and inconsistencies. + +--- +Originally published at [https://zenwave360.github.io](https://zenwave360.github.io/Domain-Driven-Design/JDL-Domain-Language/JHipster-As-IDL-for-AsyncAPIv2). \ No newline at end of file diff --git a/pages/blog/zenwave-asyncapi-code-generator.md b/pages/blog/zenwave-asyncapi-code-generator.md new file mode 100644 index 00000000000..dbfedd6ce0e --- /dev/null +++ b/pages/blog/zenwave-asyncapi-code-generator.md @@ -0,0 +1,348 @@ +--- +title: API-First with AsyncAPI and ZenWave SDK +date: 2023-08-10T06:00:00+01:00 +type: Engineering +tags: + - ZenWave SDK + - Code Generator + - Java + - Spring Cloud Streams +cover: /img/posts/zenwave/ZenWave360-AsyncAPI-SpringCloudStreams.png +canonical: https://zenwave360.github.io/Event-Driven-Architectures/AsyncAPI-Code-Generator +authors: + - name: Ivan Garcia Sainz-Aja + photo: /img/avatars/ivangsa.webp + link: https://www.linkedin.com/in/ivangarciasainzaja/ + byline: Software Architect at Sngular. Speaker. Building ZenWave 360ΒΊ +excerpt: 'With ZenWave SDK plugins, you can generate strongly typed business interfaces, payload and header DTOs and several integration patterns like Transactional Outbox, DeadLetter Queue... from AsyncAPI definitions.' +--- + +## API-First with AsyncAPI and ZenWave SDK + +With ZenWave's `spring-cloud-streams3` and `jsonschema2pojo` plugins, you can generate: +- Strongly typed **business interfaces** +- **Payload DTOs** and +- **Header objects** from AsyncAPI definitions. + +It uses Spring Cloud Streams as the default implementation, so it can connect to many different brokers via provided binders. + +And because everything is hidden behind interfaces, we can encapsulate many Enterprise Integration Patterns: + +- Transactional Outbox: with MongoDB ChangeStreams, Plain SQL, and Debezium SQL flavors +- Business DeadLetter Queues: allowing you to route different business Exceptions to different DeadLetter queues for non-retryable errors. +- Enterprise Envelope: when your organization uses a common Envelope for messages, you can still express your AsyncAPI definition in terms of your business payload. + + +See [AsyncAPI and Spring Cloud Streams 3 Configuration Options](https://zenwave360.github.io/zenwave-sdk/plugins/asyncapi-spring-cloud-streams3/#options) and [ZenWave Maven Plugin](/ZenWave-Code-Generator/Maven-Plugin) for more details. + +```xml + + io.github.zenwave360.zenwave-sdk + zenwave-sdk-maven-plugin + ${zenwave.version} + + classpath:/model/asyncapi.yml + true + true + + + + + generate-asyncapi-dtos + generate-sources + + generate + + + jsonschema2pojo + + io.zenwave360.example.core.domain.events + + true + + + + + + generate-asyncapi + generate-sources + + generate + + + spring-cloud-streams3 + + provider + + mongodb + io.zenwave360.example.core.domain.events + io.zenwave360.example.core.outbound.events + io.zenwave360.example.adapters.commands + + + + + + + + io.github.zenwave360.zenwave-sdk.plugins + asyncapi-spring-cloud-streams3 + ${zenwave.version} + + + io.github.zenwave360.zenwave-sdk.plugins + asyncapi-jsonschema2pojo + ${zenwave.version} + + + +``` + +![ZenWave StreamCloudStreams AsyncAPI](/img/posts/zenwave/spring-cloud-streams3-generated.png) + +### Provider vs Client + +Because broker-based API definitions are inherently **symmetrical**, it's difficult to establish the roles of client/server. ZenWave generates code based on `provider` and `client` roles, where a `provider` "produces events" and "consumes commands". See [API-First with AsyncAPI](API-First-with-AsyncAPI) page for more details on "publish/subscribe", "producer/consumer," and "provider/client" roles. + +> Write your AsyncAPI definitions from the `provider` perspective and then configure the code generator to generate either a `provider` or a `client`. + +If you still find it confusing which is a provider and a client, use this rule: In a given messaging scenario, there can be only one provider of a message, while there can be multiple clients. If the provider is producing messages, use the `publish` section. If the provider is consuming messages, use the `subscribe` section. + +### Spring Cloud Streams Producer: Using generated code to produce messages + +On the producer side generates: + +- Interface `ICustomerEventsProducer` to produce typed messages that uses your domain names: `onCustomerEvent`, `CustomerEventPayload`, and `CustomerEventPayloadHeaders`. +- Producer `@Component` `CustomerEventsProducer` to use Autowire in your services. + +**To produce messages, all you need to do is @Autowire, the generated producer, as part of your code.** + +Because it sits behind a business-oriented interface, this producer component can be implemented in different flavors and integration patterns like transactional outbox (for MongoDB and SQL), or enterprise envelop, depending on how you configure the Zenwave maven generator. + +```java +// Autogenerated: you can @Autowire it in your code +public interface ICustomerEventsProducer { + // headers object omitted for brevity + /** + * Customer Domain Events + */ + boolean onCustomerEvent(CustomerEventPayload payload, CustomerEventPayloadHeaders headers); + +} +``` + +```java +// Autogenerated: add it to your autoscan packages +@Component +public class CustomerEventsProducer implements ICustomerEventsProducer { + + // details omitted for brevity + + /** + * Customer Domain Events + */ + public boolean onCustomerEvent(CustomerEventPayload payload, CustomerEventPayloadHeaders headers) { + // this is one of the many flavors, you shouldn't need to worry about the details + log.debug("Sending message to topic: {}", onCustomerEventBindingName); + Message message = MessageBuilder.createMessage(payload, new MessageHeaders(headers)); + return streamBridge.send(onCustomerEventBindingName, message); + } +} +``` + +```java +// Autowire this producer in your code +@Autowired +ICustomerEventsProducer customerEventsProducer; + +// and use it to produce messages +var message = new CustomerEventPayload() + .withCustomerId("123") + // [...] set some more data + .withEventType(CustomerEventPayload.EventType.CREATED); +// notice how headers are also strongly typed +var headers = new ICustomerEventsProducer.CustomerEventPayloadHeaders() + .entityId("123") + .commonHeader("value") + .set("undocumented-header", "value"); + +customerEventsProducer.onCustomerEvent(message, headers); +``` + + +### Spring Cloud Streams Consumer: Using generated code to consume messages + +On the consumer side generates: + +- Functional Consumer `DoCustomerRequestConsumer` for Spring Cloud Streams bindings. +- Business Interface `IDoCustomerRequestConsumerService` you need to implement in order to receive strongly typed messages. + +This Functional Consumer can abstract away different integration patterns like Business Dead Letter Queue and others... depending on how you configure the Zenwave maven generator. + +**To consume messages, you need to implement the generated business interface and register it as a Spring bean.** + +```java +// Autogenerated: you need to implement and provide this business interface +public interface IOnCustomerEventConsumerService { + /** + * Customer Domain Events + */ + default void onCustomerEvent(CustomerEventPayload payload, CustomerEventPayloadHeaders headers) {}; +} +``` + +```java +// Autogenerated: add it to your autoscan packages and provide business interface implementation +@Component("on-customer-event") +public class OnCustomerEventConsumer implements Consumer> { + + // you need to implement this interface + protected IOnCustomerEventConsumerService service; + + @Override + public void accept(Message message) { + log.debug("Received message: {}", message); + try { + Object payload = message.getPayload(); + if (payload instanceof CustomerEventPayload) { + var headers = new IOnCustomerEventConsumerService.CustomerEventPayloadHeaders(); + headers.putAll(message.getHeaders()); + service.onCustomerEvent((CustomerEventPayload) payload, headers); + return; + } + log.error("Received message without any business handler: [payload: {}, message: {}]", payload.getClass().getName(), message); + } catch (Exception e) { + // error handling and dead-letter-queue routing omitted for brevity + } + } +} +``` + +```java +// Implement the business interface and add it to your context +@Component +class DoCustomerRequestConsumerService implements IDoCustomerRequestConsumerService { + + @Override + public void doCustomerRequest(CustomerRequestPayload payload, CustomerRequestPayloadHeaders headers) { + log.info("Received '{}' message with payload: {}", payload.getClass(), payload); + // [...] do something with this message + } +} +``` + +### Exception Handling with Business Dead Letter Queue + +ZenWave SDK consumers can be configured to route exceptions to different error queues. This is useful to manage non-retryable business exceptions so the stream processing is not interrupted. If your code throws an exception not configured for error routing, it will be rethrown and follow the standard error handling mechanism for your particular Spring Cloud Stream binder. + +```yaml +spring.cloud.stream.bindings: + on-customer-event-in-0: + destination: customer.events + content-type: application/json + # configuring error routing for this consumer + dead-letter-queue-error-map: > + { + 'javax.validation.ValidationException': 'on-customer-event-validation-error-out-0', + 'java.lang.Exception': 'on-customer-event-error-out-0' + } +``` + +## Populating Headers at Runtime Automatically + +ZenWave SDK provides `x-runtime-expression` for automatic header population at runtime. Values for this extension property are: + +- `$message.payload#/`: follows the same format as AsyncAPI [Correlation ID](https://www.asyncapi.com/docs/reference/specification/v2.5.0#correlationIdObject) object. +- `$tracingIdSupplier`: will use the tracing id `java.function.Supplier` configured in your Spring context. + +```yaml + CustomerEventMessage: + name: CustomerEventMessage + // [...] other properties omitted for brevity + headers: + type: object + properties: + kafka_messageKey: + type: string + description: This one will be populated automatically at runtime + x-runtime-expression: $message.payload#/customer/id + tracingId: + type: string + description: This one will be populated automatically at runtime + x-runtime-expression: $tracingIdSupplier +``` + +```xml + + myTracingIdSupplier + x-custom-runtime-expression + +``` + +```java + @Bean("myTracingIdSupplier") + public Supplier tracingIdSupplier() { + return () -> "test-tracing-id"; + } +``` + +### InMemory Producers as TestDoubles + +Alongside the generated producer, ZenWave SDK also generates an _in-memory producer captor_ that can be used as a test double and a singletone manual context so you easily include them in your unit/integration tests. + +```java +// generated class, you can use in your tests +public class ProducerInMemoryContext { + + public static final ProducerInMemoryContext INSTANCE = new ProducerInMemoryContext(); + + + private CustomerEventsProducerCaptor customerEventsProducerCaptor = new CustomerEventsProducerCaptor(); + + public T customerEventsProducer() { + return (T) customerEventsProducerCaptor; + } +} +``` + +And you can use it in your tests to instantiate your service and perform assertions. You can find [a working example here](https://github.com/ivangsa/spring-boot-mongodb-elasticsearch-kafka-example/blob/e8fa9c89e5f3d72b90ac23749f636fc7640bdf39/src/test/java/io/zenwave360/example/core/implementation/CustomerUseCasesTest.java#L70). + +```java +// example of how you can instantiate your service using the in-memory producer captor +public class InMemoryTestsManualContext extends InMemoryTestsConfig { + + // [...] other beans omitted for brevity + + public CustomerUseCasesImpl customerUseCases() { + // instantiating a bean with in-memory dependencies + return new CustomerUseCasesImpl(customerRepository(), ProducerInMemoryContext.INSTANCE.customerEventsProducer()); + } +} + +// and using it in your tests to perform assertions +public class CustomerUseCasesTest { + + // this is the in-memory producer captor wired + CustomerEventsProducerCaptor customerEventsProducer = ProducerInMemoryContext.INSTANCE.customerEventsProducer(); + + @Test + void testCustomerUseCase() { + // [...] test your use case + Assertions.assertEquals(3, customerEventsProducer.getCapturedMessages(customerEventsProducer.onCustomerEventBindingName).size()); + } +} +``` + +## Enterprise Integration Patterns + +Because access to the underlying implementation is encapsulated behind the generated interfaces, it's possible to implement many Enterprise Integration Patterns (EIP) on top of them. + +- Transactional Outbox: for MongoDB, plain JDBC, and Debezium SQL +- Business DeadLetterQueue +- Enterprise Envelop +- Automatic headers + +Jump to (https://zenwave360.github.io/zenwave-sdk/plugins/asyncapi-spring-cloud-streams3/[ZenWave SDK Spring-Cloud-Streams plugin] for more details on how to configure this. + +--- +Originally published at [https://zenwave360.github.io](https://zenwave360.github.io/Event-Driven-Architectures/AsyncAPI-Code-Generator). diff --git a/pages/docs/reference/specification/v2.6.0.md b/pages/docs/reference/specification/v2.6.0.md index fb3651e6fd2..ac73a709209 100644 --- a/pages/docs/reference/specification/v2.6.0.md +++ b/pages/docs/reference/specification/v2.6.0.md @@ -1,10 +1,10 @@ # AsyncAPI Specification -#### Disclaimer +## Disclaimer Part of this content has been taken from the great work done by the folks at the [OpenAPI Initiative](https://openapis.org). Mainly because **it's a great work** and we want to keep as much compatibility as possible with the [OpenAPI Specification](https://github.com/OAI/OpenAPI-Specification). -#### Version 2.6.0 +## Version 2.6.0 The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). @@ -33,29 +33,37 @@ It means that the [application](#definitionsApplication) allows [consumers](#def ## Definitions -#### Server +### Server + A server MAY be a message broker that is capable of sending and/or receiving between a [producer](#definitionsProducer) and [consumer](#definitionsConsumer). A server MAY be a service with WebSocket API that enables message-driven communication between browser-to-server or server-to-server. -#### Application -An application is any kind of computer program or a group of them. It MUST be a [producer](#definitionsProducer), a [consumer](#definitionsConsumer) or both. An application MAY be a microservice, IoT device (sensor), mainframe process, etc. An application MAY be written in any number of different programming languages as long as they support the selected [protocol](#definitionsProtocol). An application MUST also use a protocol supported by the [server](#definitionsServer) in order to connect and exchange [messages](#definitionsMessage). +### Application + +An application is any kind of computer program or a group of them. It MUST be a [producer](#definitionsProducer), a [consumer](#definitionsConsumer) or both. An application MAY be a microservice, IoT device (sensor), mainframe process, etc. An application MAY be written in any number of different programming languages as long as they support the selected [protocol](#definitionsProtocol). An application MUST also use a protocol supported by the [server](#definitionsServer) in order to connect and exchange [messages](#definitionsMessage). + +### Producer -#### Producer A producer is a type of application, connected to a [server](#definitionsServer), that is creating [messages](#definitionsMessage) and addressing them to [channels](#definitionsChannel). A producer MAY be publishing to multiple channels depending on the [server](#definitionsServer), protocol, and use-case pattern. -#### Consumer +### Consumer + A consumer is a type of application, connected to a [server](#definitionsServer) via a supported [protocol](#definitionsProtocol), that is consuming [messages](#definitionsMessage) from [channels](#definitionsChannel). A consumer MAY be consuming from multiple channels depending on the [server](#definitionsServer), protocol, and the use-case pattern. -#### Message -A message is the mechanism by which information is exchanged via a channel between [servers](#definitionsServer) and applications. A message MUST contain a payload and MAY also contain headers. The headers MAY be subdivided into [protocol](#definitionsProtocol)-defined headers and header properties defined by the application which can act as supporting metadata. The payload contains the data, defined by the application, which MUST be serialized into a format (JSON, XML, Avro, binary, etc.). Since a message is a generic mechanism, it can support multiple interaction patterns such as event, command, request, or response. +### Message + +A message is the mechanism by which information is exchanged via a channel between [servers](#definitionsServer) and applications. A message MUST contain a payload and MAY also contain headers. The headers MAY be subdivided into [protocol](#definitionsProtocol)-defined headers and header properties defined by the application which can act as supporting metadata. The payload contains the data, defined by the application, which MUST be serialized into a format (JSON, XML, Avro, binary, etc.). Since a message is a generic mechanism, it can support multiple interaction patterns such as event, command, request, or response. + +### Channel -#### Channel A channel is an addressable component, made available by the [server](#definitionsServer), for the organization of [messages](#definitionsMessage). [Producer](#definitionsProducer) applications send messages to channels and [consumer](#definitionsConsumer) applications consume messages from channels. [Servers](#definitionsServer) MAY support many channel instances, allowing messages with different content to be addressed to different channels. Depending on the [server](#definitionsServer) implementation, the channel MAY be included in the message via protocol-defined headers. -#### Protocol +### Protocol + A protocol is the mechanism (wireline protocol or API) by which [messages](#definitionsMessage) are exchanged between the application and the [channel](#definitionsChannel). Example protocols include, but are not limited to, AMQP, HTTP, JMS, Kafka, Anypoint MQ, MQTT, Solace, STOMP, Mercure, WebSocket, Google Pub/Sub, Pulsar. -#### Bindings -A "binding" (or "protocol binding") is a mechanism to define protocol-specific information. Therefore, a protocol binding MUST define protocol-specific information only. +### Bindings + +A "binding" (or "protocol binding") is a mechanism to define protocol-specific information. Therefore, a protocol binding MUST define protocol-specific information only. ## Specification @@ -117,7 +125,6 @@ Field Name | Type | Description tags | [Tags Object](#tagsObject) | A list of tags used by the specification with additional metadata. Each tag name in the list MUST be unique. externalDocs | [External Documentation Object](#externalDocumentationObject) | Additional external documentation. - This object MAY be extended with [Specification Extensions](#specificationExtensions). #### AsyncAPI Version String @@ -136,7 +143,7 @@ This field represents a unique universal identifier of the [application](#defini It is RECOMMENDED to use a [URN](https://tools.ietf.org/html/rfc8141) to globally and uniquely identify the application during long periods of time, even after it becomes unavailable or ceases to exist. -###### Examples +##### Examples ```json { @@ -176,7 +183,7 @@ Field Name | Type | Description This object MAY be extended with [Specification Extensions](#specificationExtensions). -##### Info Object Example: +##### Info Object Example ```json { @@ -224,7 +231,7 @@ Field Name | Type | Description This object MAY be extended with [Specification Extensions](#specificationExtensions). -##### Contact Object Example: +##### Contact Object Example ```json { @@ -253,7 +260,7 @@ Field Name | Type | Description This object MAY be extended with [Specification Extensions](#specificationExtensions). -##### License Object Example: +##### License Object Example ```json { @@ -298,7 +305,6 @@ production: protocolVersion: '1.0.0' ``` - #### Server Object An object representing a message broker, a server or any other kind of computer program capable of sending and/or receiving data. This object is used to capture details such as URIs, protocols and security configuration. Variable substitution can be used so that some details, for example usernames and passwords, can be injected by code generation tools. @@ -462,7 +468,6 @@ servers: default: v2 ``` - #### Server Variable Object An object representing a Server Variable for server URL template substitution. @@ -478,10 +483,6 @@ Field Name | Type | Description This object MAY be extended with [Specification Extensions](#specificationExtensions). - - - - #### Default Content Type A string representing the default content type to use when encoding/decoding a message's payload. The value MUST be a specific media type (e.g. `application/json`). This value MUST be used by schema parsers when the [contentType](#messageObjectContentType) property is omitted. @@ -500,11 +501,6 @@ In case a message can't be encoded/decoded using this value, schema parsers MUST defaultContentType: application/json ``` - - - - - #### Channels Object Holds the relative paths to the individual channel and their operations. Channel paths are relative to servers. @@ -538,9 +534,6 @@ user/signedup: $ref: "#/components/messages/userSignedUp" ``` - - - #### Channel Item Object Describes the operations available on a single channel. @@ -549,7 +542,7 @@ Describes the operations available on a single channel. Field Name | Type | Description ---|:---:|--- -$ref | `string` | Allows for a referenced definition of this channel item. The referenced structure MUST be in the form of a [Channel Item Object](#channelItemObject). In case a Channel Item Object field appears both in the defined object and the referenced object, the behavior is *undefined*. Resolution is done as defined by the [JSON Reference](https://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03).

**Deprecated:** Usage of the `$ref` property has been deprecated. +$ref | `string` | Allows for a referenced definition of this channel item. The referenced structure MUST be in the form of a [Channel Item Object](#channelItemObject). In case a Channel Item Object field appears both in the defined object and the referenced object, the behavior is _undefined_. Resolution is done as defined by the [JSON Reference](https://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03).

**Deprecated:** Usage of the `$ref` property has been deprecated. description | `string` | An optional description of this channel item. [CommonMark syntax](https://spec.commonmark.org/) can be used for rich text representation. servers | [`string`] | The servers on which this channel is available, specified as an optional unordered list of names (string keys) of [Server Objects](#serverObject) defined in the [Servers Object](#serversObject) (a map). If `servers` is absent or empty then this channel must be available on all servers defined in the [Servers Object](#serversObject). subscribe | [Operation Object](#operationObject) | A definition of the SUBSCRIBE operation, which defines the messages produced by the application and sent to the channel. @@ -635,7 +628,6 @@ subscribe: - $ref: '#/components/messages/login' ``` - Using explicit by-name references to the servers on which the channel is available: ```json @@ -671,10 +663,6 @@ bindings: is: queue ``` - - - - #### Operation Object Describes a publish or a subscribe operation. This provides a place to document how and why messages are sent and received. @@ -783,9 +771,6 @@ traits: - $ref: "#/components/operationTraits/kafka" ``` - - - #### Operation Trait Object Describes a trait that MAY be applied to an [Operation Object](#operationObject). This object MAY contain any property from the [Operation Object](#operationObject), except `message` and `traits`. @@ -824,9 +809,6 @@ bindings: ack: false ``` - - - #### Parameters Object Describes a map of parameters included in a channel name. @@ -873,10 +855,6 @@ user/{userId}/signup: $ref: "#/components/messages/userSignedUp" ``` - - - - #### Parameter Object Describes a parameter included in a channel name. @@ -927,9 +905,6 @@ user/{userId}/signup: $ref: "#/components/messages/userSignedUp" ``` - - - #### Server Bindings Object Map describing protocol-specific definitions for a server. @@ -960,8 +935,6 @@ Field Name | Type | Description This object MAY be extended with [Specification Extensions](#specificationExtensions). - - #### Channel Bindings Object Map describing protocol-specific definitions for a channel. @@ -992,8 +965,6 @@ Field Name | Type | Description This object MAY be extended with [Specification Extensions](#specificationExtensions). - - #### Operation Bindings Object Map describing protocol-specific definitions for an operation. @@ -1024,9 +995,6 @@ Field Name | Type | Description This object MAY be extended with [Specification Extensions](#specificationExtensions). - - - #### Message Bindings Object Map describing protocol-specific definitions for a message. @@ -1057,12 +1025,6 @@ Field Name | Type | Description This object MAY be extended with [Specification Extensions](#specificationExtensions). - - - - - - #### Message Object Describes a message received on a given channel and operation. @@ -1096,17 +1058,16 @@ The following table contains a set of values that every implementation MUST supp Name | Allowed values | Notes ---|:---:|--- [AsyncAPI 2.6.0 Schema Object](#schemaObject) | `application/vnd.aai.asyncapi;version=2.6.0`, `application/vnd.aai.asyncapi+json;version=2.6.0`, `application/vnd.aai.asyncapi+yaml;version=2.6.0` | This is the default when a `schemaFormat` is not provided. -[JSON Schema Draft 07](https://json-schema.org/specification-links.html#draft-7) | `application/schema+json;version=draft-07`, `application/schema+yaml;version=draft-07` | +[JSON Schema Draft 07](https://json-schema.org/specification-links.html#draft-7) | `application/schema+json;version=draft-07`, `application/schema+yaml;version=draft-07` | The following table contains a set of values that every implementation is RECOMMENDED to support. Name | Allowed values | Notes ---|:---:|--- [Avro 1.9.0 schema](https://avro.apache.org/docs/1.9.0/spec.html#schemas) | `application/vnd.apache.avro;version=1.9.0`, `application/vnd.apache.avro+json;version=1.9.0`, `application/vnd.apache.avro+yaml;version=1.9.0` | -[OpenAPI 3.0.0 Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject) | `application/vnd.oai.openapi;version=3.0.0`, `application/vnd.oai.openapi+json;version=3.0.0`, `application/vnd.oai.openapi+yaml;version=3.0.0` | +[OpenAPI 3.0.0 Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject) | `application/vnd.oai.openapi;version=3.0.0`, `application/vnd.oai.openapi+json;version=3.0.0`, `application/vnd.oai.openapi+yaml;version=3.0.0` | [RAML 1.0 data type](https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/) | `application/raml+yaml;version=1.0` | - ##### Message Object Example ```json @@ -1255,12 +1216,6 @@ payload: $ref: 'path/to/user-create.avsc/#UserCreate' ``` - - - - - - #### Message Trait Object Describes a trait that MAY be applied to a [Message Object](#messageObject). This object MAY contain any property from the [Message Object](#messageObject), except `payload` and `traits`. @@ -1303,13 +1258,13 @@ contentType: application/json #### Message Example Object -Message Example Object represents an example of a [Message Object](#messageObject) and MUST contain either **headers** and/or **payload** fields. +Message Example Object represents an example of a [Message Object](#messageObject) and MUST contain either **headers** and/or **payload** fields. ##### Fixed Fields Field Name | Type | Description ---|:---:|--- -headers | `Map[string, any]` | The value of this field MUST validate against the [Message Object's headers](#messageObjectHeaders) field. +headers | `Map[string, any]` | The value of this field MUST validate against the [Message Object's headers](#messageObjectHeaders) field. payload | `any` | The value of this field MUST validate against the [Message Object's payload](#messageObjectPayload) field. name | `string` | A machine-friendly name. summary | `string` | A short summary of what the example is about. @@ -1359,6 +1314,7 @@ A Tags object is a list of Tag Objects. Allows adding meta data to a single tag. ##### Fixed Fields + Field Name | Type | Description ---|:---:|--- name | `string` | **REQUIRED.** The name of the tag. @@ -1371,8 +1327,8 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ```json { - "name": "user", - "description": "User-related messages" + "name": "user", + "description": "User-related messages" } ``` @@ -1381,12 +1337,6 @@ name: user description: User-related messages ``` - - - - - - #### External Documentation Object Allows referencing an external resource for extended documentation. @@ -1423,6 +1373,7 @@ The Reference Object is defined by [JSON Reference](https://tools.ietf.org/html/ For this specification, reference resolution is done as defined by the JSON Reference specification and not by the JSON Schema specification. ##### Fixed Fields + Field Name | Type | Description ---|:---:|--- $ref | `string` | **REQUIRED.** The reference string. @@ -1449,10 +1400,10 @@ All objects defined within the components object will have no effect on the API ##### Fixed Fields Field Name | Type | Description ----|:---|--- +---|:---|--- schemas | Map[`string`, [Schema Object](#schemaObject) \| [Reference Object](#referenceObject)] | An object to hold reusable [Schema Objects](#schemaObject). servers | Map[`string`, [Server Object](#serverObject) \| [Reference Object](#referenceObject)] | An object to hold reusable [Server Objects](#serverObject). - serverVariables | Map[`string`, [Server Variable Object](#serverVariableObject) \| [Reference Object](#referenceObject)] | An object to hold reusable [Server Variable Objects](#serverVariableObject). + serverVariables | Map[`string`, [Server Variable Object](#serverVariableObject) \| [Reference Object](#referenceObject)] | An object to hold reusable [Server Variable Objects](#serverVariableObject). channels | Map[`string`, [Channel Item Object](#channelItemObject)] | An object to hold reusable [Channel Item Objects](#channelItemObject). messages | Map[`string`, [Message Object](#messageObject) \| [Reference Object](#referenceObject)] | An object to hold reusable [Message Objects](#messageObject). securitySchemes| Map[`string`, [Security Scheme Object](#securitySchemeObject) \| [Reference Object](#referenceObject)] | An object to hold reusable [Security Scheme Objects](#securitySchemeObject). @@ -1471,7 +1422,7 @@ All the fixed fields declared above are objects that MUST use keys that match th Field Name Examples: -``` +```plaintext User User_1 User_Name @@ -1752,6 +1703,7 @@ Alternatively, any time a Schema Object can be used, a [Reference Object](#refer In addition to the JSON Schema fields, the following AsyncAPI vocabulary fields MAY be used for further schema documentation: ##### Fixed Fields + Field Name | Type | Description ---|:---:|--- discriminator | `string` | Adds support for polymorphism. The discriminator is the schema property name that is used to differentiate between other schema that inherit this schema. The property name used MUST be defined at this schema and it MUST be in the `required` property list. When used, the value MUST be the name of this schema or any schema that inherits it. See [Composition and Inheritance](#schemaComposition) for more details. @@ -1763,18 +1715,18 @@ This object MAY be extended with [Specification Extensions](#specificationExtens ###### Composition and Inheritance (Polymorphism) The AsyncAPI Specification allows combining and extending model definitions using the `allOf` property of JSON Schema, in effect offering model composition. -`allOf` takes in an array of object definitions that are validated *independently* but together compose a single object. +`allOf` takes in an array of object definitions that are validated _independently_ but together compose a single object. While composition offers model extensibility, it does not imply a hierarchy between the models. To support polymorphism, AsyncAPI Specification adds the support of the `discriminator` field. When used, the `discriminator` will be the name of the property used to decide which schema definition is used to validate the structure of the model. As such, the `discriminator` field MUST be a required field. -There are are two ways to define the value of a discriminator for an inheriting instance. +There are two ways to define the value of a discriminator for an inheriting instance. - Use the schema's name. - Override the schema's name by overriding the property with a new value. If exists, this takes precedence over the schema's name. -As such, inline schema definitions, which do not have a given id, *cannot* be used in polymorphism. +As such, inline schema definitions, which do not have a given id, _cannot_ be used in polymorphism. ##### Schema Object Examples @@ -2159,25 +2111,22 @@ schemas: - color ``` - - - - #### Security Scheme Object Defines a security scheme that can be used by the operations. Supported schemes are: -* User/Password. -* API key (either as user or as password). -* X.509 certificate. -* End-to-end encryption (either symmetric or asymmetric). -* HTTP authentication. -* HTTP API key. -* OAuth2's common flows (Implicit, Resource Owner Protected Credentials, Client Credentials and Authorization Code) as defined in [RFC6749](https://tools.ietf.org/html/rfc6749). -* [OpenID Connect Discovery](https://tools.ietf.org/html/draft-ietf-oauth-discovery-06). -* SASL (Simple Authentication and Security Layer) as defined in [RFC4422](https://tools.ietf.org/html/rfc4422). +- User/Password. +- API key (either as user or as password). +- X.509 certificate. +- End-to-end encryption (either symmetric or asymmetric). +- HTTP authentication. +- HTTP API key. +- OAuth2's common flows (Implicit, Resource Owner Protected Credentials, Client Credentials and Authorization Code) as defined in [RFC6749](https://tools.ietf.org/html/rfc6749). +- [OpenID Connect Discovery](https://tools.ietf.org/html/draft-ietf-oauth-discovery-06). +- SASL (Simple Authentication and Security Layer) as defined in [RFC4422](https://tools.ietf.org/html/rfc4422). ##### Fixed Fields + Field Name | Type | Applies To | Description ---|:---:|---|--- type | `string` | Any | **REQUIRED**. The type of the security scheme. Valid values are `"userPassword"`, `"apiKey"`, `"X509"`, `"symmetricEncryption"`, `"asymmetricEncryption"`, `"httpApiKey"`, `"http"`, `"oauth2"`, `"openIdConnect"`, `"plain"`, `"scramSha256"`, `"scramSha512"`, and `"gssapi"`. @@ -2333,6 +2282,7 @@ type: scramSha512 Allows configuration of the supported OAuth Flows. ##### Fixed Fields + Field Name | Type | Description ---|:---:|--- implicit| [OAuth Flow Object](#oauthFlowObject) | Configuration for the OAuth Implicit flow. @@ -2347,6 +2297,7 @@ This object MAY be extended with [Specification Extensions](#specificationExtens Configuration details for a supported OAuth Flow ##### Fixed Fields + Field Name | Type | Applies To | Description ---|:---:|---|--- authorizationUrl | `string` | `oauth2` (`"implicit"`, `"authorizationCode"`) | **REQUIRED**. The authorization URL to be used for this flow. This MUST be in the form of an absolute URL. @@ -2455,11 +2406,11 @@ petstore_auth: ### Correlation ID Object -An object that specifies an identifier at design time that can used for message tracing and correlation. +An object that specifies an identifier at design time that can used for message tracing and correlation. For specifying and computing the location of a Correlation ID, a [runtime expression](#runtimeExpression) is used. -##### Fixed Fields +#### Fixed Fields Field Name | Type | Description ---|:---|--- @@ -2468,7 +2419,7 @@ location | `string` | **REQUIRED.** A [runtime expression](#runtimeExpression) t This object MAY be extended with [Specification Extensions](#specificationExtensions). -##### Examples +#### Examples ```json { @@ -2489,7 +2440,7 @@ This mechanism is used by [Correlation ID Object](#correlationIdObject). The runtime expression is defined by the following [ABNF](https://tools.ietf.org/html/rfc5234) syntax: -``` +```plaintext expression = ( "$message" "." source ) source = ( header-reference | payload-reference ) header-reference = "header" ["#" fragment] @@ -2499,7 +2450,7 @@ The runtime expression is defined by the following [ABNF](https://tools.ietf.org The table below provides examples of runtime expressions and examples of their use in a value: -##### Examples +#### Examples Source Location | Example expression | Notes ---|:---|:---| @@ -2531,7 +2482,6 @@ Tools that do not recognize a specific `format` MAY default back to the `type` a The formats defined by the AsyncAPI Specification are: - Common Name | `type` | [`format`](#dataTypeFormat) | Comments ----------- | ------ | -------- | -------- integer | `integer` | `int32` | signed 32 bits diff --git a/pages/docs/tools/cli/usage.md b/pages/docs/tools/cli/usage.md index 046ba19166f..f363b5ee53f 100644 --- a/pages/docs/tools/cli/usage.md +++ b/pages/docs/tools/cli/usage.md @@ -29,7 +29,7 @@ $ npm install -g @asyncapi/cli $ asyncapi COMMAND running command... $ asyncapi (--version) -@asyncapi/cli/0.52.0 linux-x64 node-v18.17.0 +@asyncapi/cli/0.52.2 linux-x64 node-v18.17.0 $ asyncapi --help [COMMAND] USAGE $ asyncapi COMMAND @@ -93,7 +93,7 @@ EXAMPLES $ asyncapi bundle ./asyncapi.yaml ./features.yaml --base ./asyncapi.yaml --reference-into-components ``` -_See code: [src/commands/bundle.ts](https://github.com/asyncapi/cli/blob/v0.52.0/src/commands/bundle.ts)_ +_See code: [src/commands/bundle.ts](https://github.com/asyncapi/cli/blob/v0.52.2/src/commands/bundle.ts)_ ## `asyncapi config` @@ -107,7 +107,7 @@ DESCRIPTION CLI config settings ``` -_See code: [src/commands/config/index.ts](https://github.com/asyncapi/cli/blob/v0.52.0/src/commands/config/index.ts)_ +_See code: [src/commands/config/index.ts](https://github.com/asyncapi/cli/blob/v0.52.2/src/commands/config/index.ts)_ ## `asyncapi config context` @@ -281,7 +281,7 @@ DESCRIPTION Convert asyncapi documents older to newer versions ``` -_See code: [src/commands/convert.ts](https://github.com/asyncapi/cli/blob/v0.52.0/src/commands/convert.ts)_ +_See code: [src/commands/convert.ts](https://github.com/asyncapi/cli/blob/v0.52.2/src/commands/convert.ts)_ ## `asyncapi diff OLD NEW` @@ -336,7 +336,7 @@ DESCRIPTION Find diff between two asyncapi files ``` -_See code: [src/commands/diff.ts](https://github.com/asyncapi/cli/blob/v0.52.0/src/commands/diff.ts)_ +_See code: [src/commands/diff.ts](https://github.com/asyncapi/cli/blob/v0.52.2/src/commands/diff.ts)_ ## `asyncapi generate` @@ -350,7 +350,7 @@ DESCRIPTION Generate typed models or other things like clients, applications or docs using AsyncAPI Generator templates. ``` -_See code: [src/commands/generate/index.ts](https://github.com/asyncapi/cli/blob/v0.52.0/src/commands/generate/index.ts)_ +_See code: [src/commands/generate/index.ts](https://github.com/asyncapi/cli/blob/v0.52.2/src/commands/generate/index.ts)_ ## `asyncapi generate fromTemplate ASYNCAPI TEMPLATE` @@ -479,7 +479,7 @@ DESCRIPTION Creates a new asyncapi file ``` -_See code: [src/commands/new/index.ts](https://github.com/asyncapi/cli/blob/v0.52.0/src/commands/new/index.ts)_ +_See code: [src/commands/new/index.ts](https://github.com/asyncapi/cli/blob/v0.52.2/src/commands/new/index.ts)_ ## `asyncapi new file` @@ -567,7 +567,7 @@ EXAMPLES $ asyncapi optimize ./asyncapi.yaml --optimization=remove-components,reuse-components,move-to-components --output=terminal --no-tty ``` -_See code: [src/commands/optimize.ts](https://github.com/asyncapi/cli/blob/v0.52.0/src/commands/optimize.ts)_ +_See code: [src/commands/optimize.ts](https://github.com/asyncapi/cli/blob/v0.52.2/src/commands/optimize.ts)_ ## `asyncapi start` @@ -581,7 +581,7 @@ DESCRIPTION Start asyncapi studio ``` -_See code: [src/commands/start/index.ts](https://github.com/asyncapi/cli/blob/v0.52.0/src/commands/start/index.ts)_ +_See code: [src/commands/start/index.ts](https://github.com/asyncapi/cli/blob/v0.52.2/src/commands/start/index.ts)_ ## `asyncapi start studio` @@ -625,5 +625,5 @@ DESCRIPTION validate asyncapi file ``` -_See code: [src/commands/validate.ts](https://github.com/asyncapi/cli/blob/v0.52.0/src/commands/validate.ts)_ +_See code: [src/commands/validate.ts](https://github.com/asyncapi/cli/blob/v0.52.2/src/commands/validate.ts)_ diff --git a/public/img/avatars/ivangsa.webp b/public/img/avatars/ivangsa.webp new file mode 100644 index 00000000000..db35d1f39c0 Binary files /dev/null and b/public/img/avatars/ivangsa.webp differ diff --git a/public/img/posts/zenwave/ZenWave360-AsyncAPI-SpringCloudStreams.png b/public/img/posts/zenwave/ZenWave360-AsyncAPI-SpringCloudStreams.png new file mode 100644 index 00000000000..5a328f7f26f Binary files /dev/null and b/public/img/posts/zenwave/ZenWave360-AsyncAPI-SpringCloudStreams.png differ diff --git a/public/img/posts/zenwave/api-first-with-asyncapi-banner.png b/public/img/posts/zenwave/api-first-with-asyncapi-banner.png new file mode 100644 index 00000000000..5466aa178b1 Binary files /dev/null and b/public/img/posts/zenwave/api-first-with-asyncapi-banner.png differ diff --git a/public/img/posts/zenwave/client-server-vs-broker-eda.excalidraw.svg b/public/img/posts/zenwave/client-server-vs-broker-eda.excalidraw.svg new file mode 100644 index 00000000000..7250e369e4e --- /dev/null +++ b/public/img/posts/zenwave/client-server-vs-broker-eda.excalidraw.svg @@ -0,0 +1,16 @@ + + + + + + + Client-Server / Request-ResponseBroker Based / Event Driven \ No newline at end of file diff --git a/public/img/posts/zenwave/jhipster-to-asyncapi-banner.png b/public/img/posts/zenwave/jhipster-to-asyncapi-banner.png new file mode 100644 index 00000000000..f29aa9ecfef Binary files /dev/null and b/public/img/posts/zenwave/jhipster-to-asyncapi-banner.png differ diff --git a/public/img/posts/zenwave/openapi-asyncapi.png b/public/img/posts/zenwave/openapi-asyncapi.png new file mode 100644 index 00000000000..1f8b8ecc18b Binary files /dev/null and b/public/img/posts/zenwave/openapi-asyncapi.png differ diff --git a/public/img/posts/zenwave/spring-cloud-streams3-generated.png b/public/img/posts/zenwave/spring-cloud-streams3-generated.png new file mode 100644 index 00000000000..460a850271a Binary files /dev/null and b/public/img/posts/zenwave/spring-cloud-streams3-generated.png differ diff --git a/scripts/build-docs.js b/scripts/build-docs.js index 6bb8d2f03c7..feca94824a4 100644 --- a/scripts/build-docs.js +++ b/scripts/build-docs.js @@ -81,10 +81,10 @@ const convertDocPosts = (docObject) => { }) } return docsArray - } +} - function addDocButtons(docPosts, treePosts){ +function addDocButtons(docPosts, treePosts){ let structuredPosts = []; let rootSections = []; @@ -160,4 +160,5 @@ const convertDocPosts = (docObject) => { return structuredPosts } -module.exports = {buildNavTree, addDocButtons} \ No newline at end of file + +module.exports = {buildNavTree, addDocButtons, convertDocPosts} \ No newline at end of file diff --git a/scripts/build-newsroom-videos.js b/scripts/build-newsroom-videos.js index 2b4b4dac4cf..073a63db9b1 100644 --- a/scripts/build-newsroom-videos.js +++ b/scripts/build-newsroom-videos.js @@ -1,7 +1,7 @@ const { writeFileSync } = require('fs'); const { resolve } = require('path'); const fetch = require('node-fetch') -export async function buildNewsroomVideos() { +async function buildNewsroomVideos() { try { let data; @@ -40,4 +40,5 @@ export async function buildNewsroomVideos() { } } -buildNewsroomVideos() \ No newline at end of file +buildNewsroomVideos() +module.exports={buildNewsroomVideos} \ No newline at end of file