Git Product home page Git Product logo

Comments (6)

jogelin avatar jogelin commented on July 20, 2024 1

Thanks a lot. I knew I was wrong in my understanding of async, I just need a confirmation and you answered it perfectly.

You rock !

from spec.

fmvilas avatar fmvilas commented on July 20, 2024 1

Not sure I understand why you chose OpenAPI if you want to do it with messages. What I said previously, is that only the first call to grab the current status of users can be documented using OpenAPI as it makes sense because it's a synchronous operation over HTTP.

Then, the rest of operations can be documented using AsyncAPI because they are events over WebSockets.

If you want to go the full way with events it's also fine and you can do it. So let's break this up into many cases:

OpenAPI + AsyncAPI

OpenAPI file to describe the first operation (grabbing the current status of users):

paths:
  /users_status
    get:
      responses:
        200:
          content:
            schema:
              $ref: "#/components/schemas/UserStatusResponse"
components:
  schemas:
    UserStatusResponse:
      type: array
      items:
        type: object
        properties:
          username:
            type: string
          full_name:
            type: string
          status:
            type: string
            enum:
              - active
              - busy
              - away
              - disconnected

And now the AsyncAPI file to describe the events:

topics:
  yourcompany.user.1.0.event.user.status.list.changed:
    subscribe:
      $ref: "#/components/messages/UserStatusListChangedMessage"
    publish:
      $ref: "#/components/messages/UserStatusListChangedMessage"
  yourcompany.user.1.0.event.user.status.changed:
    subscribe:
      $ref: "#/components/messages/UserStatusChangedMessage"
    publish:
      $ref: "#/components/messages/UserStatusChangedMessage"


components:
  messages:
    UserStatusListChangedMessage:
      payload:
        type: array
        items:
          $ref: "#/components/schemas/user"

    UserStatusChangedMessage:
      payload:
        $ref: "#/components/schemas/user"

  schemas:
    user:
      type: object
      properties:
        username:
          type: string  
        full_name:
          type: string
        status:
          type: string
          enum:
            - active
            - busy
            - away
            - disconnected

Only AsyncAPI

If you choose to only use events, then you have to change your approach. When you connect to the server using WebSockets you'll send a message saying you just connected. It's very easy to fall into the temptation of creating a message saying: "Give me the list of active users". If you want to use event-driven design the message should never be an action (i.e. Give me) but instead, it should be an event (i.e. I just connected). Here's the AsyncAPI file (roughly) for this:

topics:
  yourcompany.user.1.0.event.user.status.list.changed:
    subscribe:
      $ref: "#/components/messages/UserStatusListChangedMessage"
    publish:
      $ref: "#/components/messages/UserStatusListChangedMessage"
  yourcompany.user.1.0.event.user.status.changed:
    subscribe:
      $ref: "#/components/messages/UserStatusChangedMessage"
    publish:
      $ref: "#/components/messages/UserStatusChangedMessage"
  yourcompany.frontend.1.0.event.connected:
    subscribe:
      $ref: "#/components/messages/FrontendConnectedMessage"
    publish:
      $ref: "#/components/messages/FrontendConnectedMessage"



components:
  messages:
    UserStatusListChangedMessage:
      payload:
        type: array
        items:
          $ref: "#/components/schemas/user"

    UserStatusChangedMessage:
      payload:
        $ref: "#/components/schemas/user"

    FrontendConnectedMessage:
      payload:
        $ref: "#/components/schemas/FrontendConnected"

  schemas:
    user:
      type: object
      properties:
        username:
          type: string  
        full_name:
          type: string
        status:
          type: string
          enum:
            - active
            - busy
            - away
            - disconnected

    FrontendConnected:
      type: object

You might be wondering why I'm always duplicating publish and subscribe for each topic. Well, it's because I'm defining the API for your whole system (backend and frontend) in a single file. You could have them in separate files if you want.

Hope it helps.

from spec.

fmvilas avatar fmvilas commented on July 20, 2024

Hi @jogelin, I see a few problems here:

Your code is asynchronous, your approach does not

What you're trying to achieve, as per my understanding, is not asynchronous. You said:

the frontend display the loader and wait

If your app has to wait, then this is the first sign that it's not async. If you still want to do it like this I'll better suggest you use an HTTP API instead of WebSockets communication, because your approach is based on request/response. Of course, you can always implement RPC-style APIs using message-driven communication but that's a land I don't recommend you to walk. However, let's talk about it.

RPC

What I misunderstand is the link between request message and response message.

There's no link between messages. At least not if you don't define it. You can create a unique identifier for every message and respond back with a message referencing it. For instance, you send a message like this one:

{
  "id": "1234",
  "status": "active"
}

and the server will respond back with a message like this:

{
  "id": "5678",
  "responseTo": "1234",
  "users": [
    { "id": "fmvilas", "name": "Fran" },
    { "id": "jogelin", "name": "Jogelin" }
  ]
}

That will perfectly work, however, what you would be doing there is exactly what you get with an HTTP API. So I'd not mess around with RPC over WebSockets and would use an HTTP API instead.

Event-driven communication

If you can change how your app works I'd suggest you to events instead of requests and responses. An example could be:

  1. Your app loads and gets the current list of active users via an HTTP API.
    • This is not event-driven communication yet.
    • This could be documented using OpenAPI instead of AsyncAPI.
  2. Your app connects to the server using WebSockets and will listen for user status changes.
  3. Every time a user changes her status, the server will send a message to the frontend saying: "Hey! this is the new list of active users".
    • This event can be documented using AsyncAPI.
  4. Every time the user who is logged in changes its status, the frontend sends a message to the server saying: "Hey! user jogelin has changed his status to busy".
    • This event can be documented using AsyncAPI.

You have to think about the messages in step 3 and 4 as fire and forget. You just communicate that "something happened". You never expect a response.

I hope this sheds some light on your problem. I'm happy to help further if you need it.

from spec.

fmvilas avatar fmvilas commented on July 20, 2024

Thanks! I'm glad it helped. Feel free to ask whenever you have questions 👍

from spec.

jogelin avatar jogelin commented on July 20, 2024

Feel free to ask whenever you have questions

Hum..... so I have a question :p

Since your great answer, I am trying to specify my api with OpenApi but I would like to keep a Message Driven way....is that possible ?....:)

I am working on an app using:

  • On the frontend: Angular 2+ with a Redux system which is a system of store that used a list of actions (messages) to manage it (google if you don't know ;)).
  • On the backend: microservices scala + akka using commands to communicate between them

And the goal is to be able to communicate directly the actions of the frontend to the backend without mapping each of the actions to an http endpoint. The backend will also answer to the frontend using message formats. It is why I choosed websocket but I completly agree, websocket should not be used to synchronous requests.

So I think to have only one POST endpoint on my service which is receiving a message et returning another message.

Example:

POST to /userService/api with application/json 

requestBody =
{
  headers: {
    version: "1.0.0",
    jobName: "organisation.userService.query.user.getAll"
  },
  payload: {
    status: "active"
  }
}

responseBody 200 =
{
  headers: {
    version: "1.0.0",
    jobName: "organisation.userService.query.user.getAll"
  },
  payload: [
    {
      id: "123",
      name: "arthur"
    },
    {
      id: "456",
      name: "julien"
    }
  ]
}

My first try:

openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
paths:
  '/user/api':
    post:
      description: Publish a message
      requestBody:
        content:
          'application/json':
            schema:
              # here we can specify a list of all accepted messages
              oneOf:
              - $ref: '#/components/schemas/getAllUserMessage'
              - $ref: '#/components/schemas/getAllUserDetailMessage'
      responses:
        200:
          description: Response Message
          content:
            'application/json':
                schema:
                  # here we can specify a list of all response messages
                  oneOf:
                  - $ref: '#/components/schemas/getAllUserSuccessMessage'
                  - $ref: '#/components/schemas/getAllUserDetailSuccessMessage'
components:
  schemas:
    Headers:
      type: object
      required:
      - jobName
      - version
      properties:
        jobName:
          type: string
        version:
          type: string
    Message:
      type: object
      required:
      - headers
      - payload
      properties:
        headers:
          type: object
          $ref: '#/components/schemas/Headers'
        payload:
          type: object
    getAllUserMessage:
      allOf:
      - $ref: '#/components/schemas/Message'
      - type: object
        properties:
          headers:
            properties:
              jobName:
                enum: ['organisation.userService.query.user.getAll']
          payload:
            properties:
              status:
                type: string
    getAllUserSuccessMessage:
      allOf:
      - $ref: '#/components/schemas/Message'
      - type: object
        properties:
          headers:
            properties:
              jobName:
                enum: ['organisation.userService.query.user.getAll']
          payload:
            type: array
            items:
              $ref: '#/components/schemas/User'
    getAllUserDetailMessage:
      allOf:
      - $ref: '#/components/schemas/Message'
      - type: object
        properties:
          headers:
            properties:
              jobName:
                enum: ['organisation.userService.query.userDetail.getAll']
          payload:
            properties:
              status:
                type: string
    getAllUserDetailSuccessMessage:
      allOf:
      - $ref: '#/components/schemas/Message'
      - type: object
        properties:
          headers:
            properties:
              jobName:
                enum: ['organisation.userService.query.userDetail.getAll']
          payload:
            type: array
            items:
              $ref: '#/components/schemas/UserDetail'
    Resource:
      type: object
      required:
      - id
      - name
      properties:
        id:
          description: 'Resource identifier'
          type: string
        name:
          description: 'Resource name'
          type: string
    User:
      type: object
      $ref: '#/components/schemas/Resource'
    UserDetail:
      type: object
      $ref: '#/components/schemas/Resource'

But it looks very complex (it is why I feel that asyncapi was good for that) so if you have any advices...?

from spec.

fmvilas avatar fmvilas commented on July 20, 2024

You might want to join our Slack channel: https://async-apis-slack.herokuapp.com/

from spec.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.