Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Defining a collection of applications #658

Closed
jonaslagoni opened this issue Nov 25, 2021 · 11 comments
Closed

Defining a collection of applications #658

jonaslagoni opened this issue Nov 25, 2021 · 11 comments
Labels

Comments

@jonaslagoni
Copy link
Sponsor Member

This is an extension to The many meanings of an AsyncAPI file that tries to answer the question, should we have a way to define a larger system i.e. the need to specify applications in the context of the system.

It is a combination of the following conversations #601, comments in 618, comments in #594 and #628.

This issue tries to capture the possibility of introducing such a feature, it should not necessarily be seen as a complete feature request. Even though the issue is "small", it is a complex discussion and it can be solved in many ways. I am gonna try to break it down into more digestible chunks with examples.

Just to make sure we stay on track in the discussion, these are the main scope of the discussion. Beyond that, feel free to start a new issue or discussion focusing on a single part of the problem if you want to solve it.

Scope of discussion

  • Defining collection of applications, from now on referred to as "System"

Outside the scope of discussion

  • Relationships between applications
  • Message flow and patterns

System context

There are a couple of ways I see we can address this, and it all depends on what we see as most fitting for AsyncAPI. I will be building upon Frans syntax from the proposal to solve the publish/subscribe confusion as this restructure is necessary to further the discussion.

I will be using the social media example as a baseline and show how the suggested changes will affect such an example.

Adapting the syntax from #618 to the examples, will make this our starting point:

Common definitions (common.asyncapi.yaml)

Even though the current social media example have these definitions completely separated, having everything gathered in one file makes more sense.

asyncapi: 3.0.0

info:
  title: Organization-wide stuff
  version: 0.1.0

components:
  channels:
    commentLiked:
      address: comment/liked
      description: Notify all the services that a comment has been liked.
      servers: ['mosquitto']
      message:
        $ref: '#/components/messages/commentLiked'
    likeComment:
      address: like/comment
      description: When a comment like is received from the frontend.
      servers: ['websiteWebSocketServer']
      message:
        $ref: '#/components/messages/likeComment'
    commentLikesCountChanged:
      address: comment/{commentId}/changed
      description: When an event from the broker arrives telling us to update the comment likes count on the frontend.
      parameters: 
        commentId:
          schema: 
            $ref: '../common/schemas.yaml#/commentId'
      servers: ['mosquitto']
      message:
        $ref: '#/components/messages/commentChanged'
    updateCommentLikes:
      address: update/comment/likes
      description: Update comment likes count in the frontend.
      servers: ['websiteWebSocketServer']
      message:
        $ref: '#/components/messages/updateCommentLikes'
  messages:
    commentLiked: 
      payload: 
        $ref: '#/components/schemas/commentLikedPayload'
    likeComment: 
      payload: 
        $ref: '#/components/schemas/likeCommentPayload'
    commentChanged: 
      payload: 
        $ref: '#/components/schemas/commentChangedPayload'
    updateCommentLikes: 
      payload: 
        $ref: '#/components/schemas/updateCommentLikesPayload'
  schemas: 
    commentLikedPayload: 
      type: object
      title: commentLikedPayload
      additionalProperties: false
      properties:
        commentId: 
          allOf: 
            - $ref: '#/commentId'
            - description: Id of the comment that was liked
    likeCommentPayload: 
      type: object
      title: likeCommentPayload
      additionalProperties: false
      properties:
        commentId: 
          allOf: 
            - $ref: '#/commentId'
            - description: Id of the comment that should be liked
    commentChangedPayload: 
      type: object
      title: commentChangedPayload
      additionalProperties: false
      properties:
        commentId: 
          allOf: 
            - $ref: '#/commentId'
            - description: Id of the comment that was changed, such as when someone liked it.
    updateCommentLikesPayload:
      type: object
      title: updateCommentLikesPayload
      additionalProperties: false
      properties:
        commentId: 
          allOf: 
            - $ref: '#/commentId'
            - description: Id of the comment that was changed, such as when someone liked it.
    commentId:
      type: string

The backend - WebSocket server (backend.asyncapi.yaml)

asyncapi: 3.0.0

info:
  title: Website Backend
  version: 1.0.0

servers:
  websiteWebSocketServer:
    url: ws://mycompany.com/ws
    protocol: ws
    kind: local
  mosquitto:
    url: mqtt://test.mosquitto.org
    protocol: mqtt
    kind: remote
    bindings:
      mqtt:
        clientId: websocketServer

channels:
  commentLiked:
    $ref: 'common.asyncapi.yaml#/components/channels/commentLiked'
  likeComment:
    $ref: 'common.asyncapi.yaml#/components/channels/likeComment'
  commentLikesCountChanged:
    $ref: 'common.asyncapi.yaml#/components/channels/commentLikesCountChanged'
  updateCommentLikes:
    $ref: 'common.asyncapi.yaml#/components/channels/updateCommentLikes'

operations:
  onCommentLike:
    action: receive
    channel: likeComment
    description: When a comment like is received from the frontend.
  onCommentLikesCountChange:
    action: receive
    channel: commentLikesCountChanged
    description: When an event from the broker arrives telling us to update the comment likes count on the frontend.
  sendCommentLikesUpdate:
    action: send
    channel: updateCommentLikes
    description: Update comment likes count in the frontend.
  sendCommentLiked:
    action: send
    channel: commentLiked
    description: Notify all the services that a comment has been liked.

The frontend - WebSocket client (frontend.asyncapi.yaml)

asyncapi: 3.0.0

info:
  title: Website WebSocket Client
  version: 1.0.0

servers:
  websiteWebSocketServer:
    url: ws://mycompany.com/ws
    protocol: ws
    kind: remote

channels:
  likeComment:
    $ref: 'common.asyncapi.yaml#/components/channels/likeComment'
  updateCommentLikes:
    $ref: 'common.asyncapi.yaml#/components/channels/updateCommentLikes'

operations:
  sendCommentLike:
    action: send
    channel: likeComment
    description: Notify the backend that a comment has been liked.
  onCommentLikesUpdate:
    action: receive
    channel: updateCommentLikes
    description: Update the UI when the comment likes count is updated.

Notification service (notifications-service.asyncapi.yaml)

asyncapi: 3.0.0

info:
  title: Notifications Service
  version: 1.0.0

servers:
  mosquitto:
    url: mqtt://test.mosquitto.org
    protocol: mqtt
    kind: remote
    bindings:
      mqtt:
        clientId: notification-service

channels:
  commentLiked:
    $ref: 'common.asyncapi.yaml#/components/channels/commentLiked'

operations:
  onCommentLiked:
    action: receive
    channel: commentLiked
    description: When a "comment has been liked" message is received, it sends an SMS or push notification to the author.

Comments service (comments-service.asyncapi.yaml)

asyncapi: 3.0.0

info:
  title: Comments Service
  version: 1.0.0
  description: This service is in charge of processing all the events related to comments.

servers:
  mosquitto:
    url: mqtt://test.mosquitto.org
    protocol: mqtt
    kind: remote
    bindings:
      mqtt:
        clientId: comment-service

channels:
  commentLiked:
    $ref: 'common.asyncapi.yaml#/components/channels/commentLiked'
  commentLikesCountChanged:
    $ref: 'common.asyncapi.yaml#/components/channels/updateCommentLikes'

operations:
  onCommentLiked:
    action: receive
    channel: commentLiked
    description: Updates the likes count in the database and sends the new count to the broker.
  sendCommentLikesUpdate:
    action: send
    channel: commentLikesCountChanged
    description: Sends the new count to the broker after it has been updated in the database.

Public API (public.asyncapi.yaml)

asyncapi: 3.0.0

info:
  title: Public API 
  version: 1.0.0
  description: Public API for others to interact with the system

servers:
  mosquitto:
    url: mqtt://test.mosquitto.org
    protocol: mqtt
    kind: remote
    bindings:
      mqtt:
        clientId: public-api

channels:
  commentLiked:
    $ref: 'common.asyncapi.yaml#/components/channels/commentLiked'

operations:
  onCommentLiked:
    action: receive
    channel: commentLiked
    description: Get updates for when a comment is liked.

Through no change

Since a system is nothing but a collection of applications, how you group those applications together does not necessarily have to be defined with AsyncAPI.

A very simple example could be a physical directory where all the related AsyncAPI applications are located within the same directory. A complex solution could be something like a developed platform that groups them in some way such as in a database.

Q/A

I am gonna try to answer the questions asked in #628 as to how the changes affect the answers:

Can an application belong to multiple systems?

As there are no restrictions on how you define a collection of applications, it is possible.

How many separate applications are described?

Depends on how you interpret what a collection of applications is.

Which channels are used by which applications?

Through the reusability, we can make a basic search for it.

What messages are read/sent onto the channels by each application?

Depending on the structure, you can look at the applications themselves.

Where are those channels located?

By default, channels are located in all servers unless specifically defined as only related to one server.

If I want to send/receive a message from a specific application, which protocol/server/channel do I connect to?

As each application "know" which operations others can perform, and over what channels. Indirectly we know which servers those operations are performed on.

Can I define servers that all applications must connect to?

No, however, if you can use references for servers you "can".

Through a new simple keyword: Stay with application-centric

The same way we allow you to define a dictionary, we can allow the definition of a system. Say we introduce a simple applications keyword on the root level of the specification, based on your example @nictownsend.

The spec will keep its application-centric approach, where it does not know anything about a system context.

The new structure of the spec will look like this:

asyncapi: 3.0.0
info: ...
##### System-specific ######
applications: ...

##### Application-specific #####
servers: ...
channels: ...
operations: ...

##### Common-specific #####
components:
  servers: ...  
  channels: ...  
  schemas: ...  
  messages: ...  
  securitySchemes: ...  
  parameters: ...  
  correlationIds: ...  
  operationTraits: ...  
  messageTraits: ...  
  serverBindings: ...  
  channelBindings: ...  
  operationBindings: ...  
  messageBindings: ...

Building upon the existing types mentioned #628 (comment), we extend it with a system type. With the new keyword, we add some system types similar to the application and dictionary types.

The system as a non-dictionary

One of the ways to define the system, is by only including applications. The other files still use the common.asyncapi.yaml file.

This means we introduce a new system file:

The system (system.asyncapi.yaml)

asyncapi: 3.0.0

info:
  title: The small social media system definitions
  description: The small social media system
  version: 1.0.0

applications:  
  - $ref: './backend.asyncapi.yaml' 
  - $ref: './frontend.asyncapi.yaml'
  - $ref: './notifications-service.asyncapi.yaml' 
  - $ref: './comments-service.asyncapi.yaml' 
  - $ref: './public.asyncapi.yaml' 

System as a dictionary

You can also use the system definition like a dictionary, meaning we replace the common.asyncapi.yaml file with system.asyncapi.yaml.

Each individual application would then reference the system file instead of the common.asyncapi.yaml.

The system (system.asyncapi.yaml)

asyncapi: 3.0.0

info:
  title: The small social media system definitions
  description: The small social media system
  version: 1.0.0

applications:  
  - $ref: './backend.asyncapi.yaml' 
  - $ref: './frontend.asyncapi.yaml'
  - $ref: './notifications-service.asyncapi.yaml' 
  - $ref: './comments-service.asyncapi.yaml' 
  - $ref: './public.asyncapi.yaml' 

components:
  channels:
    commentLiked:
      address: comment/liked
      description: Notify all the services that a comment has been liked.
      servers: ['mosquitto']
      message:
        $ref: '#/components/messages/commentLiked'
    likeComment:
      address: like/comment
      description: When a comment like is received from the frontend.
      servers: ['websiteWebSocketServer']
      message:
        $ref: '#/components/messages/likeComment'
    commentLikesCountChanged:
      address: comment/{commentId}/changed
      description: When an event from the broker arrives telling us to update the comment likes count on the frontend.
      parameters: 
        commentId:
          schema: 
            $ref: '../common/schemas.yaml#/commentId'
      servers: ['mosquitto']
      message:
        $ref: '#/components/messages/commentChanged'
    updateCommentLikes:
      address: update/comment/likes
      description: Update comment likes count in the frontend.
      servers: ['websiteWebSocketServer']
      message:
        $ref: '#/components/messages/updateCommentLikes'
  messages:
    commentLiked: 
      payload: 
        $ref: '#/components/schemas/commentLikedPayload'
    likeComment: 
      payload: 
        $ref: '#/components/schemas/likeCommentPayload'
    commentChanged: 
      payload: 
        $ref: '#/components/schemas/commentChangedPayload'
    updateCommentLikes: 
      payload: 
        $ref: '#/components/schemas/updateCommentLikesPayload'
  schemas: 
    commentLikedPayload: 
      type: object
      title: commentLikedPayload
      additionalProperties: false
      properties:
        commentId: 
          allOf: 
            - $ref: '#/commentId'
            - description: Id of the comment that was liked
    likeCommentPayload: 
      type: object
      title: likeCommentPayload
      additionalProperties: false
      properties:
        commentId: 
          allOf: 
            - $ref: '#/commentId'
            - description: Id of the comment that should be liked
    commentChangedPayload: 
      type: object
      title: commentChangedPayload
      additionalProperties: false
      properties:
        commentId: 
          allOf: 
            - $ref: '#/commentId'
            - description: Id of the comment that was changed, such as when someone liked it.
    updateCommentLikesPayload:
      type: object
      title: updateCommentLikesPayload
      additionalProperties: false
      properties:
        commentId: 
          allOf: 
            - $ref: '#/commentId'
            - description: Id of the comment that was changed, such as when someone liked it.
    commentId:
      type: string

The backend - WebSocket server (backend.asyncapi.yaml)

asyncapi: 3.0.0

info:
  title: Website Backend
  version: 1.0.0

servers:
  websiteWebSocketServer:
    url: ws://mycompany.com/ws
    protocol: ws
  mosquitto:
    url: mqtt://test.mosquitto.org
    protocol: mqtt
    bindings:
      mqtt:
        clientId: websocketServer
channels:
  commentLiked:
    $ref: 'system.asyncapi.yaml#/components/channels/commentLiked'
  likeComment:
    $ref: 'system.asyncapi.yaml#/components/channels/likeComment'
  commentLikesCountChanged:
    $ref: 'system.asyncapi.yaml#/components/channels/commentLikesCountChanged'
  updateCommentLikes:
    $ref: 'system.asyncapi.yaml#/components/channels/updateCommentLikes'

operations:
  onCommentLike:
    action: receive
    channel: likeComment
    description: When a comment like is received from the frontend.
  onCommentLikesCountChange:
    action: receive
    channel: commentLikesCountChanged
    description: When an event from the broker arrives telling us to update the comment likes count on the frontend.
  sendCommentLikesUpdate:
    action: send
    channel: updateCommentLikes
    description: Update comment likes count in the frontend.
  sendCommentLiked:
    action: send
    channel: commentLiked
    description: Notify all the services that a comment has been liked.

The system as a mix

This type is just a mix between the two approaches, where the system for some channels/messages, etc, is used as a dictionary, other times applications define it themselves.

Q/A

Can an application belong to multiple systems?

As there are no restrictions on how you define a collection of applications, so it can be possible.

How many separate applications are described?

This can be looked up directly in the system definition.

Which channels are used by which applications?

You would have to go through each application to see what channels they use.

What messages are read/sent onto the channels by each application?

You would still have to walk through each application to find this information. From a design point, through references you could have this information present (if used as a dictionary) in the system file.

Where are those channels located?

By default, channels are located in all servers unless specifically defined as only related to one server. Nothing changes here. You would still need to look up each application how they define it.

If I want to send/receive a message from a specific application, which protocol/server/channel do I connect to?

As each application hold the information about which operations is performed, and over what channels. Nothing changes here.

Can I define servers that all applications must connect to?

No.

Through a change of perspective: Moving from application-centric to system-centric

To move from an application-centric to system-centric, would require a breaking change in the specification. This would mean that we would not be able to describe a standalone application.

The new structure of the spec will look like this:

asyncapi: 3.0.0
info: ...

##### System-specific ######
applications: ...
channels: ...

##### Application-specific #####
servers: ...
operations: ...

##### Common-specific #####
components:
  servers: ...  
  channels: ...  
  schemas: ...  
  messages: ...  
  securitySchemes: ...  
  parameters: ...  
  correlationIds: ...  
  operationTraits: ...  
  messageTraits: ...  
  serverBindings: ...  
  channelBindings: ...  
  operationBindings: ...  
  messageBindings: ...

I will center the system around each "server". Meaning I will have two system definitions, one for the MQTT broker and its applications and one centered around the WebSocket server. There are other ways you can structure this, but I will focus on these two.

This would change all our applications to be defined like so:

System definitions for MQTT (system.mqtt.asyncapi.yaml)

Notice the MQTT system has the backend.asyncapi.yaml application referenced as it is part of both the MQTT and WebSocket system.

asyncapi: 3.0.0

info:
  title: The small social media system definitions
  description: The small social media system
  version: 1.0.0

applications:  
  - $ref: './backend.asyncapi.yaml'
  - $ref: './notifications-service.asyncapi.yaml' 
  - $ref: './comments-service.asyncapi.yaml' 
  - $ref: './public.asyncapi.yaml' 

channels:
  commentLiked:
    address: comment/liked
    description: Notify all the services that a comment has been liked.
    servers: ['mosquitto']
    message:
      $ref: '#/components/messages/commentLiked'
  commentLikesCountChanged:
    address: comment/{commentId}/changed
    description: When an event from the broker arrives telling us to update the comment likes count on the frontend.
    parameters: 
      commentId:
        schema: 
          $ref: '../common/schemas.yaml#/commentId'
    servers: ['mosquitto']
    message:
      $ref: '#/components/messages/commentChanged'

components:
  messages:
    commentLiked: 
      payload: 
        $ref: '#/components/schemas/commentLikedPayload'
    likeComment: 
      payload: 
        $ref: '#/components/schemas/likeCommentPayload'
    commentChanged: 
      payload: 
        $ref: '#/components/schemas/commentChangedPayload'
    updateCommentLikes: 
      payload: 
        $ref: '#/components/schemas/updateCommentLikesPayload'
  schemas: 
    commentLikedPayload: 
      type: object
      title: commentLikedPayload
      additionalProperties: false
      properties:
        commentId: 
          allOf: 
            - $ref: '#/commentId'
            - description: Id of the comment that was liked
    likeCommentPayload: 
      type: object
      title: likeCommentPayload
      additionalProperties: false
      properties:
        commentId: 
          allOf: 
            - $ref: '#/commentId'
            - description: Id of the comment that should be liked
    commentChangedPayload: 
      type: object
      title: commentChangedPayload
      additionalProperties: false
      properties:
        commentId: 
          allOf: 
            - $ref: '#/commentId'
            - description: Id of the comment that was changed, such as when someone liked it.
    updateCommentLikesPayload:
      type: object
      title: updateCommentLikesPayload
      additionalProperties: false
      properties:
        commentId: 
          allOf: 
            - $ref: '#/commentId'
            - description: Id of the comment that was changed, such as when someone liked it.
    commentId:
      type: string

The backend - WebSocket server (websocket.system.asyncapi.yaml)

Notice that the backend server is now both seen as an application and a system. This system comprises of backend server itself and the frontend application.

The backend server is also seen as an application to the MQTT broker - as part of the mqtt.system.asyncapi.yaml.

asyncapi: 3.0.0

info:
  title: Website backend WebSocket server
  version: 1.0.0

servers:
  websiteWebSocketServer:
    url: ws://mycompany.com/ws
    protocol: ws
    kind: local
  mosquitto:
    url: mqtt://test.mosquitto.org
    protocol: mqtt
    kind: remote
    bindings:
      mqtt:
        clientId: backend-service

applications:  
  - $ref: './frontend.asyncapi.yaml'

channels:
  likeComment:
    address: like/comment
    description: When a comment like is received from the frontend.
    servers: ['websiteWebSocketServer']
    message:
      $ref: '#/components/messages/likeComment'
  updateCommentLikes:
    address: update/comment/likes
    description: Update comment likes count in the frontend.
    servers: ['websiteWebSocketServer']
    message:
      $ref: '#/components/messages/updateCommentLikes'

operations:
  onCommentLike:
    action: receive
    channel: likeComment
    description: When a comment like is received from the frontend.
  onCommentLikesCountChange:
    action: receive
    channel: commentLikesCountChanged
    description: When an event from the broker arrives telling us to update the comment likes count on the frontend.
  sendCommentLikesUpdate:
    action: send
    channel: updateCommentLikes
    description: Update comment likes count in the frontend.
  sendCommentLiked:
    action: send
    channel: commentLiked
    description: Notify all the services that a comment has been liked.

components:
  messages:
    likeComment: 
      payload: 
        $ref: '#/components/schemas/likeCommentPayload'
    updateCommentLikes: 
      payload: 
        $ref: '#/components/schemas/updateCommentLikesPayload'
  schemas: 
    likeCommentPayload: 
      type: object
      title: likeCommentPayload
      additionalProperties: false
      properties:
        commentId: 
          allOf: 
            - $ref: '#/commentId'
            - description: Id of the comment that should be liked
    updateCommentLikesPayload:
      type: object
      title: updateCommentLikesPayload
      additionalProperties: false
      properties:
        commentId: 
          allOf: 
            - $ref: '#/commentId'
            - description: Id of the comment that was changed, such as when someone liked it.
    commentId:
      type: string

The frontend - WebSocket client (frontend.asyncapi.yaml)

asyncapi: 3.0.0

info:
  title: Website WebSocket Client
  version: 1.0.0

servers:
  websiteWebSocketServer:
    url: ws://mycompany.com/ws
    protocol: ws
    kind: remote

operations:
  sendCommentLike:
    action: send
    channel: likeComment
    description: Notify the backend that a comment has been liked.
  onCommentLikesUpdate:
    action: receive
    channel: updateCommentLikes
    description: Update the UI when the comment likes count is updated.

Notification service (notifications-service.asyncapi.yaml)

asyncapi: 3.0.0

info:
  title: Notifications Service
  version: 1.0.0

servers:
  mosquitto:
    url: mqtt://test.mosquitto.org
    protocol: mqtt
    kind: remote
    bindings:
      mqtt:
        clientId: notification-service

operations:
  onCommentLiked:
    action: receive
    channel: commentLiked
    description: When a "comment has been liked" message is received, it sends an SMS or push notification to the author.

Comments service (comments-service.asyncapi.yaml)

asyncapi: 3.0.0

info:
  title: Comments Service
  version: 1.0.0
  description: This service is in charge of processing all the events related to comments.

servers:
  mosquitto:
    url: mqtt://test.mosquitto.org
    protocol: mqtt
    kind: remote
    bindings:
      mqtt:
        clientId: comment-service

operations:
  onCommentLiked:
    action: receive
    channel: commentLiked
    description: Updates the likes count in the database and sends the new count to the broker.
  sendCommentLikesUpdate:
    action: send
    channel: commentLikesCountChanged
    description: Sends the new count to the broker after it has been updated in the database.

Public API (public.asyncapi.yaml)

asyncapi: 3.0.0

info:
  title: Public API 
  version: 1.0.0
  description: Public API for others to interact with the system

servers:
  mosquitto:
    url: mqtt://test.mosquitto.org
    protocol: mqtt
    kind: remote
    bindings:
      mqtt:
        clientId: public-api

operations:
  onCommentLiked:
    action: receive
    channel: commentLiked
    description: Get updates for when a comment is liked.

Q/A

Can an application belong to multiple systems?

Yes, as we can see with the backend application, it is a system in itself for WS, and part of the MQTT system.

It does however require that channel ID's never overlap.

How many separate applications are described?

This can be looked up directly in the system definition.

Which channels are used by which applications?

You would still have to go through each application to see which operations they perform and over what channels.

What messages are read/sent onto the channels by each application?

Since the system defines the channels and their message definitions this can be looked up there. No need to access the application definitions, they only define operations.

Where are those channels located?

By default, channels are located in all servers unless specifically defined as only related to one server. Nothing changes here.

If I want to send/receive a message from a specific application, which protocol/server/channel do I connect to?

As each application hold the information about which operations is performed, and over what channels. Nothing changes here.

Can I define servers that all applications must connect to?

No, because in many cases depending on the application, they need to define specific information, such as for MQTT, `clientId`.

My conclusion

Even though something like a system-centric approach could be an interesting concept, my answer would be we should stay with an application-centric approach. It would then be up to the individuals, how they group multiple applications together. We just provide the building blocks for you to define applications.

Yes, it will mean you need to duplicate many of the definitions, but through $ref we can limit it, at least enough to where I don't see it as a massive disadvantage.

What do you think?

@gregmeldrum
Copy link

@jonaslagoni Well thought out and I like all of the examples! I want to bring up the discovery usecase. The approach we took for discovery is two phased: 1. grab all of the data you can from the running system (system-centric) 2. use this data to define the application boundaries of your eda (application-centric).

The output of phase 1 is AsyncAPI (using the discovery tool). In this case the "discoverer" doesn't necessarily know the application boundaries, but is just accumulating data to feed into phase 2 where these boundaries are defined. Phase 1 discovery is a transitory use of the spec but I think worthy of consideration. I guess one could argue that AsyncAPI may not be the right output format of phase 1, but no one wants yet another specification.

@fmvilas
Copy link
Member

fmvilas commented Jan 24, 2022

@gregmeldrum wouldn't the discovery use case be the same as the dictionary use case defined above? You just throw message definitions on an AsyncAPI file but don't know who's publishing or subscribing, i.e., you don't specify operations.

@jessemenning
Copy link

@jonaslagoni , I apologize for this long overdue response to this. It is a very good analysis.

be up to the individuals, how they group multiple applications together

This works in the context of files stored in github--you can group the files however you want, and the application==file scoping makes sense. But for non-file implementations, such as registries, things get trickier. I don't see a way to address the following use cases if there is a registry with a REST API:

  • Represent all the microservice applications within a system
  • Respond which applications are using a channel.
  • etc.

Looking at @boyney123 , @dalelane and @SolaceProducts work, I think it would be a mistake to take only a file-exclusive approach. If we don't have a spec object to represent application interactions with registries gets very clunky (zipped binary file responses?).

Not having an application object also means that applications can't refer to each other, which eliminates the possibility of OpenAPI style inheritance. For instance, defining the bare requirements in an "interface" definition, which is then implemented by the actual application.

@jonaslagoni
Copy link
Sponsor Member Author

jonaslagoni commented Feb 21, 2022

I think a live discussion is in order to try and deep dive into some of the perspectives of this. There is a lot to cover, so one might not be enough, but let's start with that and see where it takes us.

So let's do a regular 1-hour live stream where we can discuss the different aspects and what it can mean to define multiple applications.

I suggest we do it a week from now on Monday 28. February 16 UTC.

The goal of the discussion is to reach a solid suggestion (RFC-1) about what should be done. I outlined my own thought in the above post, but many of the things are abstract and maybe hard to grasp, so this would be a platform to raise concerns and have a friendly discussion 🙂

@fmvilas @derberg these loose meetings that we have from time to time that is not recurring, anything I need to do to set that up?

@jonaslagoni
Copy link
Sponsor Member Author

Good perspective @jessemenning 👏

Looking at @boyney123 , @dalelane and https://github.com/SolaceProducts work, I think it would be a mistake to take only a file-exclusive approach
Remember, the file structure approach was only to highlight, it all comes down to how you as a platform provider would bundle applications together 🙂 Studio could have one way of bundling applications, and solace another.

But yea, it is definitely a valid argument, supporting it explicitly, would probably make sense in a lot of cases 🤔

@jonaslagoni
Copy link
Sponsor Member Author

jonaslagoni commented Mar 2, 2022

I suggest we do it a week from now on Monday 28. February 16 UTC.

The recording is out: https://www.youtube.com/watch?v=Vl8Nn3GOJiE

Recap:

Monday the 7 of March at 16 UTC, we are gonna have another meeting to continue the discussion!

https://postman.zoom.us/j/89829979800

@smoya
Copy link
Member

smoya commented Mar 4, 2022

I just watched the recording. Thanks for all the discussion you folks drove in there 👏 💯.

I wanted to make a couple of suggestions on the approach for declaring applications.

1. Introduce a new key for defining system info

Instead of using the info keyword we have now, instead introduce a new system keyword which can include several fields such as name, id, and some metadata like team, or up to any other fields we can think of (yeah, we could always use extensions, I know).

I'm suggesting this because:

  1. We already saw the version field might not match with what a system means. So having a special validation based on an external (from the Info object point of view) field (is it applications defined?) is not very solid. In this case, either systemor info could be defined.
  2. I see systems as a really good solution for defining what a bounded context in a product means, in terms of applications. The same would apply for a team. Answering questions like: "What are the applications team X manages/have" will be then be possible; in consequence, generating documentation, etc etc.
  3. The fact that we let the user to add more data, can empower extensibility.

Example:

asyncapi: 3.0.0
system:
  id: "com.mycompany.payments"
  name: "Payment gateway"
  description: "The main Payment gateway available on example.com"
  metadata:
    team: "platform"

2. To create a new object for each application instead of directly using an array of references.

This is a little bit more tricky and controversial. But I want to drop some of my thoughts (not super elaborated) here.

Define a new application object on the applications array with some extra optional fields like name, description, and also metadata.

I'm suggesting this because:

  1. Some applications could be reused across a whole company, however, the usage each system might do with them could be completely different. Allowing users to add their own descriptions, notes, etc will be IMHO useful in their context.

Example:

asyncapi: 3.0.0

# ...

applications:
  - name: "Core payment service"  # maybe we can remove this, just use the `info.name`
    application: 
      - $ref: './backend.asyncapi.yaml'
  - name: "Analytics server" 
    description: "The service where all the payments are recorded to. This application is widely shared across the company"
    application: 
      - $ref: './analytics.asyncapi.yaml'

@boyney123
Copy link

boyney123 commented Mar 4, 2022

Just watched the video nice work and thanks for recording.

Here are some of my thoughts/questions whilst watching


When I saw the use of applications in the spec it was clear to me what it meant and some of its use cases. I can see applications as a great way of teams grouping domains in the future (this feature was a key ask from the community over on EventCatalog).

You asked "How could the system.yml" be useful to users, here were my thoughts

  • If we know applications without the domain like you showed then we could visualise domains / EDA better
  • Some devs will be focused on their application asyncapi file, and some will want a higher picture (system.yml) in this case.
  • New documentation methods to help us show/document groups of Async APIS (EDA) stuff.
  • Opens the door to more value I think across the board.

You showed examples of context in the front-end and back-end AsyncAPI files, I'm not sure why/what that would bring? Unless I'm misunderstanding something there? I thought the applications pattern was simple.


Versioning ha... yeah looks interesting, hard to know if systems would be versioned or not, from my own experience I would version my frontend and backend in your example but probably not the domain/system.... as @fmvilas was saying version concept is too loose IMO (in this context)


@smoya I like your idea in number 2 of having additional fields in the applications array rather than just a direct pointer to the asyncapi files, allowing us to extend applications in the future without a direct tie to async-api files (if that makes sense).


Also domains are a very common terminology in EDA showing examples of domain.yml might make things easier to grasp vs system.yml but maybe that's just me...

@jonaslagoni
Copy link
Sponsor Member Author

jonaslagoni commented Mar 7, 2022

Monday the 7 of March at 16 UTC, we are gonna have another meeting to continue the discussion!

As no one was able to join there is no recording as there were no meeting 😆 Let's see when in the future it makes sense to hold another one.

Introduce a new key for defining system info

I think this makes sense, IF and only IF there is a huge difference in which properties for the information about the system vs application. I.e. metadata I don't believe makes sense, as it most likely varies a lot between use-cases and cant be well defined, so extensions fit better there in my opinion. That said, creating a separate system info object might still make sense!

To create a new object for each application instead of directly using an array of references

There is something interesting about this cause it actually solves one of the problems that I recently faced.

How do you group, specific applications together (and should you)?

For example, if I had 3 applications that comprise my payment system would I want to group these 3 together? In your suggestion, it enables at least one level of groupings, but not systems in systems (as I read it).

I don't like the applications in applications, maybe another keyword would make more sense 🙂

You showed examples of context in the front-end and back-end AsyncAPI files, I'm not sure why/what that would bring? Unless I'm misunderstanding something there? I thought the applications pattern was simple.

@boyney123 the context came into play when we discussed how the AsyncAPI documents could be used without knowledge of the system, especially when change of perspective is applied.

@jonaslagoni
Copy link
Sponsor Member Author

I am gonna stop pursuing this issue for now as it seems we do not have enough clear use-cases for it, so it would not make sense to force this change as of now. I still believe this is eventually needed, and if you ever encounter such a use case please let us know below.

If you wish to champion this issue, feel free to take it over 🙂

@github-actions
Copy link

This issue has been automatically marked as stale because it has not had recent activity 😴

It will be closed in 120 days if no further activity occurs. To unstale this issue, add a comment with a detailed explanation.

There can be many reasons why some specific issue has no activity. The most probable cause is lack of time, not lack of interest. AsyncAPI Initiative is a Linux Foundation project not owned by a single for-profit company. It is a community-driven initiative ruled under open governance model.

Let us figure out together how to push this issue forward. Connect with us through one of many communication channels we established here.

Thank you for your patience ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants