O blog da AWS

Modernização de aplicativos com o AWS AppSync Events

Por Ricardo Marques, Senior AppMod & Serverless Specialist na na HAQM Web Services (AWS)

No mundo digital acelerado de hoje, as organizações estão enfrentando desafios para modernizar seus aplicativos. Um problema comum é a mudança suave da comunicação síncrona para a assíncrona sem alterações substanciais no cliente ou no frontend. Ao modernizar aplicativos, geralmente é necessário passar de um modelo de comunicação síncrona para um assíncrono. No entanto, essa transição pode ser complexa, especialmente quando o cliente ou o frontend se comunicam de forma síncrona. Adaptar o código atual para comunicação assíncrona exige tempo e recursos significativos.

O AWS AppSync Events ajuda a enfrentar esse desafio, permitindo que você crie APIs orientadas por eventos que podem fazer a ponte entre modelos de comunicação síncrona e assíncrona. Com o AppSync Events, você pode modernizar sua arquitetura de backend para aproveitar os padrões assíncronos e, ao mesmo tempo, manter a compatibilidade com os clientes síncronos existentes.

Visão geral

A solução inclui uma API que converte solicitações síncronas do cliente em solicitações assíncronas de backend usando o AppSync Events.

Para demonstrar a integração entre a API e o backend, estou simulando o processamento do backend usando um fluxo de trabalho assíncrono do AWS Step Functions. Esse fluxo de trabalho recebe um evento de nome e sobrenome, espera 10 segundos e publica um evento com nome completo no canal de eventos do AppSync. Para receber notificações de eventos, a API se inscreve no canal AppSync. Ao mesmo tempo, o backend lida com eventos de forma assíncrona.

Figura 1: Representação de uma API integrando um frontend síncrono com um backend assíncrono usando o AWS AppSync Events.

Figura 1: Representação de uma API integrando um frontend síncrono com um backend assíncrono usando o AWS AppSync Events.

  1. O HAQM API Gateway faz uma solicitação síncrona ao AWS Lambda e aguarda a resposta.
  2. A função Lambda inicia a execução do fluxo de trabalho assíncrono.
  3. Depois de iniciar a execução do fluxo de trabalho, o Lambda se conecta ao AppSync e cria um canal para receber notificações assíncronas (os canais são efêmeros e ilimitados). Aqui, ele cria um canal por solicitação usando o ID de execução do fluxo de trabalho).
  4. O fluxo de trabalho é executado de forma assíncrona, chamando outros fluxos de trabalho.
  5. Após a conclusão do fluxo de trabalho principal, ele envia uma solicitação POST para a API de eventos do AppSync com o resultado do processamento. O POST é feito no canal que foi criado pela função Lambda usando o ID de execução do fluxo de trabalho.
  6. O AppSync recebe a solicitação POST e envia uma notificação ao assinante, que nesse caso é a função Lambda. Todo o processo deve ser concluído dentro do limite de tempo limite das funções do Lambda que você definiu.
  7. O Lambda envia a resposta para o API Gateway, que está aguardando a resposta síncrona.

Para entender melhor o Event API WebSocket Protocol usado nessa solução, consulte esta documentação do AppSync.

Você pode acessar o repositório do GitHub por meio deste link: AppSync_Sync_Async_Integration.

O repositório inclui um arquivo README abrangente que orienta você no processo de instalação e configuração da solução anterior.

Pré-requisitos

Para seguir este passo a passo, você precisa dos seguintes pré-requisitos:

Com o código completo, incluindo o API Gateway e o Step Functions, no GitHub, esta postagem aborda apenas os componentes principais: a API AppSync Events e a função Lambda.

Passo a passo

As etapas a seguir orientam você nessa solução.

Criação de uma API de eventos do AppSync com autorização de chave de API

Uma API de eventos do AppSync permite chamadas usando chave de API, grupos de usuários do HAQM Cognito, autorizador Lambda, OIDC ou AWS Identity and Access Management (IAM). Essa solução usa a chave de API.

A infraestrutura como código (IaC) foi criada usando o Terraform. No entanto, no momento em que escrevi esta postagem, não havia o recurso da API Terraform AppSync Event disponível. Portanto, os recursos da API AppSync Event foram criados com o AWS CloudFormation, que é importado e implementado pelo Terraform.

No recurso aws:appsync:api, defina o nome da API e o método Auth:

Resources:
  #Creating the AppSync Events API
  EventAPI:
    Type: AWS::AppSync::Api
    Properties:
      Name: SyncAsyncAPI
      EventConfig:
        AuthProviders:
          - AuthType: API_KEY
        ConnectionAuthModes:
          - AuthType: API_KEY
        DefaultPublishAuthModes:
          - AuthType: API_KEY
        DefaultSubscribeAuthModes:
          - AuthType: API_KEY
#Creating the Events API Namespace
  DefaultNamespace:
    Type: AWS::AppSync::ChannelNamespace
    Properties:
      Name: AsyncEvents
      ApiId: !GetAtt EventAPI.ApiId
  
  #Creating the Events API APIKey
  EventAPIKey:
    Type: AWS::AppSync::ApiKey
    Properties:
      ApiId: !GetAtt EventAPI.ApiId
      Expires: 1748950672
      Description: 'API Key for Event API'

  #Creating the SecretsManager to store the APIKey
  SecretsManagerAPIKey:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: 'AppSyncEventAPIKEY'
      SecretString: !GetAtt EventAPIKey.ApiKey
YAML

Para que o Host DNS, o Realtime Endpoint e o Secret Manager criados referenciados pelo modelo do Terraform, imprima-os:

Outputs:
  ApiARN:
    Description: 'The ARN ID'
    Value: !GetAtt EventAPI.ApiArn

  AppSyncHost:
    Description: 'The API Endpoint'
    Value: !GetAtt EventAPI.Dns.Http

  AppSyncRealTimeEndpoint:
    Description: 'The Real-time Endpoint'
    Value: !GetAtt EventAPI.Dns.Realtime

  SecretsManagerARN:
    Description: 'The ARN of the Secrets Manager entry'
    Value: !Ref SecretsManagerAPIKey
YAML

As principais informações necessárias da API de eventos do AppSync são:

  1. DNS do host: esse DNS é usado para enviar eventos para o canal da API por meio de solicitações HTTP Post.
  2. Endpoint em tempo real: esse endpoint é um endpoint do WebSocket em que a função Lambda se conecta para receber os eventos publicados no canal AppSync.
  3. Chave de API: essa chave é usada não apenas nas solicitações Post HTTP, mas também para se conectar e se inscrever no canal AppSync.

API Lambda Sync/Async

Nessa solução, a função Lambda executa duas tarefas:

  1. Inicie um fluxo de trabalho assíncrono
  2. Inscreva-se em um canal de eventos por meio do WebSocket

Para lidar com a conexão WebSocket, use a biblioteca websocket-client, que é uma poderosa biblioteca Python desenvolvida para trabalhar com WebSockets.

O isolamento da solicitação é mantido usando o mesmo UUID para o nome do fluxo de trabalho e o nome do canal do AppSync.

try:
        handler = WebSocketHandler()
        sfn_response = wf.start_workflow_async(event["body"])
        
        if sfn_response["status"] == "started":
            handler.execution_name = sfn_response["id"]
            handler.start_websocket_connection()
            
            return {
                'statusCode': 200,
                'body': json.dumps({ 
                        "id": handler.execution_name,
                        "nome completo": handler.final_name
                        })
            }
        else:
            raise ValueError("Workflow failed to start")
Python

Primeiro, para inicializar a conexão WebSocket, os subprotocolos devem ser definidos:

  • WEBSOCKET_PROTOCOL
  • Cabeçalhos:
    • Host: O DNS do host do AppSync (mesmo com uma conexão WebSocket, o host HTTP deve ser enviado)
    • x-api-key: a chave de API criada para a API Event.
    • Protocolo Sec-Websocket: WEBSOCKET_PROTOCOL
def start_websocket_connection(self) -> None:
        try: 
            """Initialize and start WebSocket connection."""
            header_str = self._create_connection_header()
            
            self.ws = websocket.WebSocketApp(
                os.environ["API_URL"],
                subprotocols=[WEBSOCKET_PROTOCOL, f'header-{header_str}'],
                on_open=self.on_open,
                on_message=self.on_message,
                on_error=self.on_error,
                on_close=self.on_close
)
            self.ws.run_forever()
        except Exception as e:
            return e
Python

Depois que a conexão do WebSocket for estabelecida, uma primeira mensagem com o tipo CONNECTION_INIT_TYPE deverá ser enviada.

Para se inscrever no canal pelo qual nossa função é notificada quando o fluxo de trabalho do Step Functions termina, envie uma segunda mensagem com o tipo SUBSCRIBE_TYPE, um ID, o nome do canal e a autorização.

Para obter mais informações sobre os tipos de mensagem, leia esta documentação do AppSync.

def on_open(self, ws: websocket.WebSocketApp) -> None:
        try:
            """Handle WebSocket connection opening and send initial messages."""
            logger.info("Connection opened")
            
            # Send connection initialization
            connection_init = {"type": CONNECTION_INIT_TYPE}
            ws.send(json.dumps(connection_init))

            # Send subscription
            subscription_msg = {
                "type": SUBSCRIBE_TYPE,
                "id": self.execution_name,
                "channel": f"{os.environ["APPSYNC_NAMESPACE"]}/{self.execution_name}",
                "authorization": {
                    "x-api-key": APIKEY,
                    "host": os.environ["API_HOST"]
                }
            }
            
            logger.info("Sending subscription")
            ws.send(json.dumps(subscription_msg))
        except Exception as e:
            self.on_error = e
Python

Depois de receber a mensagem confirmando a assinatura, aguarde as mensagens com os dados do tipo. Sempre que uma mensagem desse tipo chegar, execute a lógica para identificar se o fluxo de trabalho foi executado com êxito e, em seguida, feche a conexão.

def on_message(self, ws: websocket.WebSocketApp, message: str) -> None:
        """Handle incoming WebSocket messages."""
        logger.info("Message received: %s", message)
        try:
            message_dict = json.loads(message)
            required_keys = ["id", "type", "event"]
            
            if all(key in message_dict for key in required_keys):
                event_json = json.loads(message_dict["event"])
                
                if (message_dict["id"] == self.execution_name and 
                    message_dict["type"] == "data"):
                    
                    self.final_name = event_json["nome_completo"]
                    logger.info("Message received: %s", self.final_name)
                    logger.info("Successfully received return message")
                    logger.info("Ending processing")
                    
                    self.message_queue = {
                        "status": SUCCESS_STATUS,
                        "executionID": message_dict["id"]
                    }
                    ws.close()
        except json.JSONDecodeError as e:
            logger.error("Failed to parse message: %s", str(e))
        except Exception as e:
            logger.error("Error processing message: %s", str(e))
Python

Conclusão

Neste post, você aprendeu a usar arquiteturas orientadas por eventos e os recursos do AWS AppSync Events para integrar padrões de comunicação síncrona e assíncrona em seus aplicativos. Isso permite que você modernize seus sistemas sem a necessidade de modificações extensivas em sua base de código de frontend existente. Explore as demonstrações e a documentação fornecidas no repositório do GitHub para obter uma compreensão mais profunda de como os Eventos do AppSync podem ser aplicados aos seus casos de uso específicos.

Para saber mais sobre arquiteturas sem servidor e padrões de invocação assíncrona, consulte Serverless Land.

Este conteúdo foi traduzido da postagem original do blog, que pode ser encontrada aqui.

Biografia do Autor

Ricardo Marques é Senior AppMod & Serverless Specialist na AWS, com mais de 17 anos de experiência em desenvolvimento de software, arquiteturas de soluções escaláveis, cloud native, microsserviços, Serverless e segurança. Ele trabalha apoiando clientes de toda América Latina, ajudando-os em sua jornada para a nuvem.

http://www.linkedin.com/in/ricardo-marques-45846425/

Biografia do tradutor

Daniel Abib é Arquiteto de Soluções Sênior e Especialista em HAQM Bedrock na AWS, com mais de 25 anos trabalhando com gerenciamento de projetos, arquiteturas de soluções escaláveis, desenvolvimento de sistemas e CI/CD, microsserviços, arquitetura Serverless & Containers e especialização em Machine Learning. Ele trabalha apoiando Startups, ajudando-os em sua jornada para a nuvem.

http://www.linkedin.com/in/danielabib/

Biografia do Revisor

Luis Miguel,  Com mais de uma década de experíência em tecnologia sempre buscando otimizações, resiliência e performance das aplicação. Atualmente, foco em observabilidade, auxiliando times a desenvolverem uma visão clara e acionável de suas aplicações. Meu objetivo é traduzir complexidade em clareza, permitindo que empresas tomem decisões mais assertivas sobre seus sistemas.