Kyrux
Voltar

APIs (REST, GraphQL, gRPC, SOAP)

🧪 Experimental — parte da camada core (kyrux/core), rotulada "experimental" pelo próprio USE.md do framework. API pode mudar entre versões sem aviso.

Os quatro protocolos vivem como adapters (core/adapters/...) sobre o core.Core — um orquestrador novo, paralelo ao bootstrap tradicional, com seu próprio ciclo de vida (Init → Configure → Start → Shutdown).

import "kyrux/core"

c := core.New()
// ... registra REST/GraphQL/gRPC/SOAP ...
c.Run()          // Start em todos os módulos ativados
defer c.Shutdown() // Shutdown em ordem reversa

Dois jeitos de ativar um módulo:

  • Sugar — namespaces prontos (core.API.REST(addr), core.Auth.OAuth2(...)) pra protocolos leves/stdlib.
  • core.UseModule[T] — genérico, usado pelos protocolos com dependência pesada (GraphQL, gRPC), que não ganham sugar de propósito, pra não puxar essas dependências pra quem não usa.

Nenhum dos quatro lê variável de ambiente — endereço/porta/opções são sempre passados como argumento Go no seu próprio código.

REST — core/adapters/restapi

Não é um protocolo novo — é o mesmo core/router.Router de sempre, só que rodando no próprio *http.Server/porta do core.Core, orquestrado por Run()/Shutdown() em vez de fw.Run().

import "kyrux/core"

c := core.New()
r, err := c.API.REST(":8080") // *router.Router — mesma API de sempre

r.Handle("GET /ping", func(ctx *router.Context) {
    ctx.JSON(200, map[string]string{"pong": "kyrux"})
})

c.Run()

GraphQL — core/adapters/apigraphql

Sem sugar (dependência pesada — graphql-go traz parser e template próprios). Você monta o schema com a API do graphql-go/graphql e entrega pronto pro adapter.

import (
    "github.com/graphql-go/graphql"
    "kyrux/core"
    "kyrux/core/adapters/apigraphql"
)

queryType := graphql.NewObject(graphql.ObjectConfig{
    Name: "Query",
    Fields: graphql.Fields{
        "versao": &graphql.Field{
            Type: graphql.String,
            Resolve: func(p graphql.ResolveParams) (any, error) {
                return "kyrux-core", nil
            },
        },
    },
})
schema, _ := graphql.NewSchema(graphql.SchemaConfig{Query: queryType})

c := core.New()
_, err := core.UseModule[*graphql.Schema](c, apigraphql.New(":8081", schema), "api.graphql.principal")
c.Run()

Serve POST / com corpo {"query": "..."}. A GraphiQL IDE vem habilitada por padrão, sem opção de desligar. Roda no próprio *http.Server — não é montado no router do REST.

gRPC — core/adapters/apigrpc

Também sem sugar (HTTP/2 + runtime protobuf). O adapter só hospeda o *grpc.Server — você gera o código do serviço a partir de um .proto (protoc + protoc-gen-go + protoc-gen-go-grpc) e registra do jeito normal do gRPC.

import (
    "kyrux/core"
    "kyrux/core/adapters/apigrpc"
)

c := core.New()
srv, err := core.UseModule[*grpc.Server](c, apigrpc.New(":9090"), "api.grpc.principal")
pb.RegisterGreeterServer(srv, &minhaImplementacao{}) // registro padrão do gRPC

// gRPC e REST coexistem no mesmo Core, portas diferentes:
r, _ := c.API.REST(":8080")

c.Run()

Shutdown chama GracefulStop() sem timeout — adicione seu próprio watchdog se precisar de um limite de tempo.

SOAP — core/soap + core/adapters/soapclient

Implementação própria (encoding/xml + net/http, sem lib de terceiros — o ecossistema SOAP em Go é fraco). Pensado pra casos como webservices legados/governamentais (SEFAZ/NF-e, INSS).

Servidor — não é uma porta separada, é um http.Handler que você monta no router do REST:

import (
    "kyrux/core"
    "kyrux/core/soap"
)

server := soap.NewServer()
server.Handle("SomarRequest", func(ctx context.Context, requestXML []byte) ([]byte, error) {
    // parse do requestXML, monta a resposta
    return responseXML, nil
})

c := core.New()
r, _ := c.API.REST(":8080")
r.HandlePrefix("/soap", server) // mesmo padrão usado pra montar métricas Prometheus

c.Run()

O roteamento é pelo nome do elemento raiz dentro de <Body> — pragmático, não segue WSDL/SOAPAction "oficial".

Cliente, cru ou via sugar do Core:

// Cru:
client := soap.NewClient(endpoint, httpClient)

// Sugar (zero custo — é stdlib puro):
client, err := c.API.SOAPClient("principal", endpoint)

var resp somarResponse
err = client.Call(ctx, "Somar", somarRequest{A: 4, B: 5}, &resp)
// erro do lado do servidor vira *soap.Fault (errors.As)

Nenhum é lido do .env

Como o NoSQL, os quatro protocolos não têm convenção API_REST_ADDR-style — endereço, schema, opções do grpc.Server: tudo passado como argumento Go no seu próprio código, na hora de montar o core.Core.