cloudbroker-sdk 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +18 -0
- package/README.md +83 -5
- package/docs/COST_PER_FEATURE.md +296 -0
- package/example-checkout-app/.env.example +13 -0
- package/example-checkout-app/README.md +225 -0
- package/example-checkout-app/package.json +28 -0
- package/example-checkout-app/server.js +209 -0
- package/example-checkout-app/test/e2e.js +97 -0
- package/package.json +13 -4
- package/src/client.js +17 -6
- package/src/cloud-wrapper.js +531 -5
- package/src/pricing-cloud.js +165 -10
- package/src/test-azure-real.js +169 -0
- package/src/test.js +264 -0
|
@@ -4,7 +4,16 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
branches: [main]
|
|
6
6
|
paths:
|
|
7
|
+
# Sem barra inicial: filtros de path do GitHub Actions são relativos à
|
|
8
|
+
# raiz do repo e '/package.json' não casa com nada — o workflow deixou de
|
|
9
|
+
# disparar por completo (o 0.6.0 foi mergeado e nunca publicado).
|
|
10
|
+
# 'package.json' já cobre só a raiz: sem '**', o glob não atravessa
|
|
11
|
+
# diretórios, que era a intenção original de ancorar.
|
|
7
12
|
- 'package.json'
|
|
13
|
+
# Escape hatch: quando o filtro acima não dispara (foi o caso do 0.6.0, que
|
|
14
|
+
# entrou no main sem publicar), dá pra publicar a versão que está no main sem
|
|
15
|
+
# precisar de um bump artificial só pra acionar o gatilho.
|
|
16
|
+
workflow_dispatch:
|
|
8
17
|
|
|
9
18
|
jobs:
|
|
10
19
|
publish:
|
|
@@ -22,6 +31,15 @@ jobs:
|
|
|
22
31
|
node-version: 20
|
|
23
32
|
registry-url: 'https://registry.npmjs.org'
|
|
24
33
|
|
|
34
|
+
# Gate de teste antes do publish — este pacote roda DENTRO da aplicação
|
|
35
|
+
# de produção do cliente; publicar sem rodar a suíte já era um risco
|
|
36
|
+
# aberto (nenhum CI de teste existia neste repo).
|
|
37
|
+
- name: Instalar dependências de teste
|
|
38
|
+
run: npm ci || npm install
|
|
39
|
+
|
|
40
|
+
- name: Rodar testes
|
|
41
|
+
run: npm test
|
|
42
|
+
|
|
25
43
|
- name: Publish to npm
|
|
26
44
|
run: npm publish
|
|
27
45
|
env:
|
package/README.md
CHANGED
|
@@ -106,14 +106,17 @@ CloudBrokerAI.trackDeploy("cb-key-xxxx", {
|
|
|
106
106
|
});
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
##
|
|
109
|
+
## Cost per Feature — Rastreie Custo por Feature
|
|
110
110
|
|
|
111
|
-
Atribua custo de **AWS**
|
|
112
|
-
do
|
|
111
|
+
Atribua custo de **AWS** e **Azure** a cada feature/módulo sua. O SDK envolve um
|
|
112
|
+
client do provedor e, a cada chamada, estima o custo (requests + transferência) e
|
|
113
113
|
registra um evento — mesmo padrão fire-and-forget do wrapper de IA.
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
**👉 [Guia Completo](./docs/COST_PER_FEATURE.md)** — Setup (5 min), exemplos, FAQ.
|
|
116
|
+
|
|
117
|
+
> Escopo atual — AWS: **S3** e **DynamoDB** (`us-east-2`); Azure: **Blob Storage**
|
|
118
|
+
> e **Cosmos DB** (`eastus2`). Armazenamento GB-mês e atribuição automática
|
|
119
|
+
> (stack-trace) estão fora do MVP.
|
|
117
120
|
|
|
118
121
|
```js
|
|
119
122
|
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
@@ -146,6 +149,81 @@ Cada chamada gera um evento `POST /api/cloud/events` com `service`, `operation`,
|
|
|
146
149
|
`product`, `team`, `label`, `requests`, `bytesIn`/`bytesOut`, `costUSD` estimado e
|
|
147
150
|
`latencyMs`. O `cbLabel` nunca é repassado ao AWS SDK.
|
|
148
151
|
|
|
152
|
+
### Azure — Blob Storage e Cosmos DB
|
|
153
|
+
|
|
154
|
+
O Azure SDK não tem um `.send()` único como o AWS SDK v3: cada sub-client tem
|
|
155
|
+
métodos próprios. Por isso o `wrapAzure` intercepta a **cadeia de sub-clients** —
|
|
156
|
+
as fábricas (`getContainerClient`, `database`/`container`) devolvem sub-clients já
|
|
157
|
+
embrulhados, e só os métodos de data-plane conhecidos são rastreados. Qualquer
|
|
158
|
+
método fora dessa lista passa intocado.
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
import { BlobServiceClient } from "@azure/storage-blob";
|
|
162
|
+
import { CloudBrokerCloud } from "cloudbroker-sdk";
|
|
163
|
+
|
|
164
|
+
const blobSvc = CloudBrokerCloud.wrapAzure(
|
|
165
|
+
BlobServiceClient.fromConnectionString(process.env.AZURE_STORAGE_CONNECTION),
|
|
166
|
+
{ apiKey: "cb-key-xxxx", product: "checkout", team: "mobile" },
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// label opcional dentro do objeto de options nativo do método
|
|
170
|
+
await blobSvc
|
|
171
|
+
.getContainerClient("docs")
|
|
172
|
+
.getBlockBlobClient("nf.pdf")
|
|
173
|
+
.uploadData(buf, { cbLabel: "upload-nota-fiscal" });
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Cosmos DB usa o **`requestCharge` (RUs) real** que o servidor devolve em cada
|
|
177
|
+
resposta — custo por unidade cobrada de verdade, não aproximação por request:
|
|
178
|
+
|
|
179
|
+
```js
|
|
180
|
+
import { CosmosClient } from "@azure/cosmos";
|
|
181
|
+
|
|
182
|
+
const cosmos = CloudBrokerCloud.wrapAzure(new CosmosClient(conn), {
|
|
183
|
+
apiKey: "cb-key-xxxx", product: "checkout",
|
|
184
|
+
});
|
|
185
|
+
const orders = cosmos.database("loja").container("pedidos");
|
|
186
|
+
|
|
187
|
+
await orders.items.create({ id: "123" }, { cbLabel: "criar-pedido" });
|
|
188
|
+
await orders.items.query("SELECT * FROM c").fetchAll(); // RUs do query também contam
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
O `expressMiddleware` (abaixo) funciona igual pros dois provedores — mesma
|
|
192
|
+
prioridade de `label`/`product`/`team`, sem precisar de `cbLabel` manual.
|
|
193
|
+
|
|
194
|
+
> **Precisão e premissas — leia antes de tratar o número como fechado:**
|
|
195
|
+
>
|
|
196
|
+
> - **Cosmos:** as RUs são as **reais** devolvidas pelo servidor em cada resposta
|
|
197
|
+
> (`requestCharge`). O **preço por RU** assume o modelo **serverless**. Em
|
|
198
|
+
> *throughput provisionado* (manual ou autoscale) a Azure cobra RU/s reservada
|
|
199
|
+
> por hora, independentemente do consumo — esse modelo ainda não é representado,
|
|
200
|
+
> então o custo calculado não corresponde à fatura nesse caso.
|
|
201
|
+
> - **Blob:** estimado por classe de operação (write/read/list), assumindo tier
|
|
202
|
+
> **Hot** e redundância **LRS**. Cool/Archive e GRS/ZRS têm outro preço.
|
|
203
|
+
> - **Transferência de saída:** cobrada sobre todo `bytesOut`. A Azure não cobra
|
|
204
|
+
> saída intra-região e concede franquia mensal — para um app rodando na mesma
|
|
205
|
+
> região do Storage, este termo **superestima**.
|
|
206
|
+
> - **Região:** só `eastus2` tem tabela de preço. Em outra região o evento é
|
|
207
|
+
> registrado com `costUSD: null` e `billingUnit: "*-region-unpriced"` — a
|
|
208
|
+
> operação aparece no relatório, mas sem custo inventado.
|
|
209
|
+
> - **Reconciliação:** a tela compara o estimado contra o custo real do Cost
|
|
210
|
+
> Management e mostra o **desvio**; o ajuste da tabela é manual (não há
|
|
211
|
+
> recalibração automática).
|
|
212
|
+
> - **Listagem:** `listBlobsFlat`/`listContainers` devolvem iterator preguiçoso —
|
|
213
|
+
> o evento só é emitido quando uma página é de fato buscada. Percorrendo com
|
|
214
|
+
> `for await`, contamos **1 operação** mesmo que o SDK pagine internamente;
|
|
215
|
+
> use `.byPage()` para contagem por página.
|
|
216
|
+
> - **Cosmos `for await` direto** (`getAsyncIterator`) não é instrumentado —
|
|
217
|
+
> use `fetchAll()`/`fetchNext()`.
|
|
218
|
+
> - **Delete** no tier Hot é grátis: sai com custo `0` e
|
|
219
|
+
> `billingUnit: "blob-free"`, sem sumir do relatório.
|
|
220
|
+
>
|
|
221
|
+
> ⚠️ **Ainda não calibrado contra uma fatura Azure real.** A estrutura é
|
|
222
|
+
> exercitada contra os SDKs oficiais (`@azure/storage-blob`, `@azure/cosmos`) na
|
|
223
|
+
> suíte de testes, mas os valores da tabela de preço Azure não foram conferidos
|
|
224
|
+
> contra a página oficial nem contra uma fatura. Trate o custo Azure como ordem
|
|
225
|
+
> de grandeza até rodar a reconciliação na sua conta.
|
|
226
|
+
|
|
149
227
|
### Atribuição automática por requisição (Express)
|
|
150
228
|
|
|
151
229
|
Em vez de passar `cbLabel` em cada `.send()`, use o middleware: todas as chamadas
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# CloudBroker SDK — Rastreie Custo por Feature
|
|
2
|
+
|
|
3
|
+
## O Que É?
|
|
4
|
+
|
|
5
|
+
Instrumentar sua aplicação com o CloudBroker SDK permite rastrear **quanto cada feature/módulo seu custa em AWS e Azure**.
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
// Seu código
|
|
9
|
+
await s3.putObject({ ... }, { cbLabel: 'invoice-generation' });
|
|
10
|
+
// CloudBroker registra: feature 'invoice-generation' custou $0.00042
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Result:** Dashboard mostra "invoice-generation: $X/mês".
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Setup Rápido (5 minutos)
|
|
18
|
+
|
|
19
|
+
### 1. Instalar
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install cloudbroker-sdk
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 2. Gerar API Key
|
|
26
|
+
|
|
27
|
+
1. Acesse https://app.cloudbroker.app.br
|
|
28
|
+
2. Configurações → API Keys
|
|
29
|
+
3. Criar nova key do tipo **SDK** (só Admin pode criar keys SDK)
|
|
30
|
+
4. Copiar: `cb_live_...` — **o valor é exibido uma única vez**; depois só o
|
|
31
|
+
prefixo mascarado fica visível. Se perder, gere outra.
|
|
32
|
+
|
|
33
|
+
### 3. Instrumentar Backend
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
37
|
+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
38
|
+
import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb";
|
|
39
|
+
import { CloudBrokerCloud } from "cloudbroker-sdk";
|
|
40
|
+
|
|
41
|
+
// S3
|
|
42
|
+
const s3 = CloudBrokerCloud.wrapAWS(new S3Client({ region: "us-east-2" }), {
|
|
43
|
+
apiKey: process.env.CLOUDBROKER_API_KEY,
|
|
44
|
+
product: "checkout-service", // seu app
|
|
45
|
+
team: "payments", // seu squad
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// DynamoDB
|
|
49
|
+
const dynamodb = CloudBrokerCloud.wrapAWS(
|
|
50
|
+
DynamoDBDocumentClient.from(new DynamoDBClient({ region: "us-east-2" })),
|
|
51
|
+
{ apiKey: process.env.CLOUDBROKER_API_KEY, product: "checkout-service", team: "payments" }
|
|
52
|
+
);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 4. Adicionar Labels
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
// S3 upload com label
|
|
59
|
+
await s3.send(new PutObjectCommand({
|
|
60
|
+
Bucket: 'invoices',
|
|
61
|
+
Key: `${orderId}.pdf`,
|
|
62
|
+
Body: pdfBuffer,
|
|
63
|
+
}), {
|
|
64
|
+
cbLabel: 'invoice-generation', // ← Identifica a feature
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// DynamoDB insert com label
|
|
68
|
+
await dynamodb.send(new PutCommand({
|
|
69
|
+
TableName: 'Orders',
|
|
70
|
+
Item: { orderId, status: 'pending' },
|
|
71
|
+
}), {
|
|
72
|
+
cbLabel: 'order-processing', // ← Identifica a feature
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 5. Ver Custos
|
|
77
|
+
|
|
78
|
+
Dashboard: **CloudBroker App → Custo por Código**
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Feature: order-processing
|
|
82
|
+
Requests: 3,456
|
|
83
|
+
Total: $87.50
|
|
84
|
+
Per Request: $0.0253 ✅
|
|
85
|
+
|
|
86
|
+
Feature: invoice-generation
|
|
87
|
+
Requests: 1,234
|
|
88
|
+
Total: $10.20
|
|
89
|
+
Per Request: $0.00828 ✅
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Azure — Blob Storage e Cosmos DB
|
|
95
|
+
|
|
96
|
+
O SDK da Azure não tem um `.send()` único como o AWS SDK v3, então o `wrapAzure`
|
|
97
|
+
intercepta a **cadeia de sub-clients**: as fábricas (`getContainerClient`,
|
|
98
|
+
`database`/`container`) devolvem sub-clients já instrumentados.
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
import { BlobServiceClient } from "@azure/storage-blob";
|
|
102
|
+
import { CloudBrokerCloud } from "cloudbroker-sdk";
|
|
103
|
+
|
|
104
|
+
const blobSvc = CloudBrokerCloud.wrapAzure(
|
|
105
|
+
BlobServiceClient.fromConnectionString(process.env.AZURE_STORAGE_CONNECTION),
|
|
106
|
+
{ apiKey: "cb_live_...", product: "checkout", team: "mobile" },
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
await blobSvc
|
|
110
|
+
.getContainerClient("docs")
|
|
111
|
+
.getBlockBlobClient("nf.pdf")
|
|
112
|
+
.uploadData(buf, { cbLabel: "upload-nota-fiscal" });
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Cosmos DB usa o `requestCharge` (RUs) **real** devolvido pelo servidor:
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
import { CosmosClient } from "@azure/cosmos";
|
|
119
|
+
|
|
120
|
+
const cosmos = CloudBrokerCloud.wrapAzure(new CosmosClient(conn), {
|
|
121
|
+
apiKey: "cb_live_...", product: "checkout",
|
|
122
|
+
});
|
|
123
|
+
const pedidos = cosmos.database("loja").container("pedidos");
|
|
124
|
+
|
|
125
|
+
await pedidos.items.create({ id: "123" }, { cbLabel: "criar-pedido" });
|
|
126
|
+
await pedidos.items.query("SELECT * FROM c").fetchAll();
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
O `expressMiddleware` funciona igual para AWS e Azure.
|
|
130
|
+
|
|
131
|
+
**Premissas que mudam o número** (detalhe completo no
|
|
132
|
+
[README](../README.md#cost-per-feature--rastreie-custo-por-feature)):
|
|
133
|
+
|
|
134
|
+
| Item | Situação |
|
|
135
|
+
|---|---|
|
|
136
|
+
| Cosmos — RUs | Reais (`requestCharge` do servidor) |
|
|
137
|
+
| Cosmos — preço por RU | Assume **serverless**. Throughput provisionado não é modelado |
|
|
138
|
+
| Blob | Estimado por classe de operação; assume tier **Hot** + **LRS** |
|
|
139
|
+
| Egress | Cobrado sobre todo `bytesOut` (a Azure não cobra intra-região) |
|
|
140
|
+
| Região | Só `eastus2` tem preço; outras saem com `costUSD: null` |
|
|
141
|
+
| `for await` no Cosmos | Não instrumentado — use `fetchAll()`/`fetchNext()` |
|
|
142
|
+
| Calibração | ⚠️ **Não conferido contra fatura Azure real ainda** |
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Convenção de Labels
|
|
147
|
+
|
|
148
|
+
Use nomes **descritivos** que refletem features/módulos:
|
|
149
|
+
|
|
150
|
+
| Feature | Label | Exemplos de Operações |
|
|
151
|
+
|---------|-------|----------------------|
|
|
152
|
+
| Checkout | `order-processing` | Criar pedido, persistir dados |
|
|
153
|
+
| Geração de Invoice | `invoice-generation` | Upload de PDF, armazenamento |
|
|
154
|
+
| Detecção de Fraude | `fraud-detection` | Análise de padrões, lookup histórico |
|
|
155
|
+
| Analytics | `analytics-tracking` | Coleta de eventos, agregação |
|
|
156
|
+
| Admin Panel | `admin-reports` | Geração de relatórios, queries |
|
|
157
|
+
|
|
158
|
+
**Regras:**
|
|
159
|
+
- ✅ Use labels **finitos** (não criar um label por request)
|
|
160
|
+
- ✅ Labels **descritivos** (não `x1`, `x2`, etc)
|
|
161
|
+
- ✅ Use **kebab-case** (`order-processing`, não `orderProcessing`)
|
|
162
|
+
|
|
163
|
+
❌ **EVITAR:**
|
|
164
|
+
```javascript
|
|
165
|
+
// Ruim: label infinito (1 por request)
|
|
166
|
+
cbLabel: `order-${order.id}`
|
|
167
|
+
|
|
168
|
+
// Ruim: não descritivo
|
|
169
|
+
cbLabel: 'x1'
|
|
170
|
+
|
|
171
|
+
// Bom: finito e descritivo
|
|
172
|
+
cbLabel: 'order-processing'
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Exemplo Completo
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
import express from 'express';
|
|
181
|
+
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
182
|
+
import { DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb';
|
|
183
|
+
import { CloudBrokerCloud } from 'cloudbroker-sdk';
|
|
184
|
+
|
|
185
|
+
const app = express();
|
|
186
|
+
|
|
187
|
+
// Setup
|
|
188
|
+
const s3 = CloudBrokerCloud.wrapAWS(new S3Client(), {
|
|
189
|
+
apiKey: process.env.CLOUDBROKER_API_KEY,
|
|
190
|
+
product: 'checkout-service',
|
|
191
|
+
team: 'payments',
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const dynamodb = CloudBrokerCloud.wrapAWS(
|
|
195
|
+
DynamoDBDocumentClient.from(new DynamoDBClient()),
|
|
196
|
+
{ apiKey: process.env.CLOUDBROKER_API_KEY, product: 'checkout-service', team: 'payments' }
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// POST /orders — cria pedido com 2 features diferentes
|
|
200
|
+
app.post('/orders', async (req, res) => {
|
|
201
|
+
const order = { orderId: `order-${Date.now()}`, ...req.body };
|
|
202
|
+
|
|
203
|
+
// Feature 1: Persistir pedido
|
|
204
|
+
await dynamodb.send(new PutCommand({
|
|
205
|
+
TableName: 'Orders',
|
|
206
|
+
Item: order,
|
|
207
|
+
}), {
|
|
208
|
+
cbLabel: 'order-processing',
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Feature 2: Gerar invoice
|
|
212
|
+
await s3.send(new PutObjectCommand({
|
|
213
|
+
Bucket: 'invoices',
|
|
214
|
+
Key: `${order.orderId}.json`,
|
|
215
|
+
Body: JSON.stringify(order),
|
|
216
|
+
}), {
|
|
217
|
+
cbLabel: 'invoice-generation',
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
res.json({ ok: true, orderId: order.orderId });
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
app.listen(3000);
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Result no Dashboard:**
|
|
227
|
+
- `order-processing`: $X/mês
|
|
228
|
+
- `invoice-generation`: $Y/mês
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## FAQ
|
|
233
|
+
|
|
234
|
+
**P: Quanto tempo leva pra aparecer no dashboard?**
|
|
235
|
+
A: ~5 minutos (batch em DynamoDB).
|
|
236
|
+
|
|
237
|
+
**P: E se não colocar label?**
|
|
238
|
+
A: Ainda rastreia como `null` (agrupa sem label).
|
|
239
|
+
|
|
240
|
+
**P: Posso mudar o label depois?**
|
|
241
|
+
A: Não — é histórico. Use um novo label a partir de agora.
|
|
242
|
+
|
|
243
|
+
**P: Qual é o overhead?**
|
|
244
|
+
A: <1% CPU (fire-and-forget, não bloqueia requests).
|
|
245
|
+
|
|
246
|
+
**P: Funciona em todas as regiões?**
|
|
247
|
+
A: Hoje há tabela de preço para `us-east-2` (AWS) e `eastus2` (Azure). Em outra
|
|
248
|
+
região a operação é registrada com `costUSD: null` e `billingUnit`
|
|
249
|
+
`*-region-unpriced` — aparece no relatório, mas sem custo estimado (preferimos
|
|
250
|
+
não exibir o preço de outra região como se fosse o seu).
|
|
251
|
+
|
|
252
|
+
**P: E se der erro no rastreamento?**
|
|
253
|
+
A: Silencioso — seu request continua normal (fail-soft).
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Troubleshooting
|
|
258
|
+
|
|
259
|
+
### Nenhum dado aparecendo no dashboard?
|
|
260
|
+
|
|
261
|
+
1. ✅ API Key configurada?
|
|
262
|
+
```bash
|
|
263
|
+
echo $CLOUDBROKER_API_KEY
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
2. ✅ CloudBrokerCloud.wrapAWS() chamado?
|
|
267
|
+
```javascript
|
|
268
|
+
const s3 = CloudBrokerCloud.wrapAWS(new S3Client(), { apiKey, product, team });
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
3. ✅ cbLabel adicionado?
|
|
272
|
+
```javascript
|
|
273
|
+
await s3.send(cmd, { cbLabel: 'seu-label' });
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
4. ✅ Esperou 5+ minutos?
|
|
277
|
+
(CloudBroker faz batch, não é real-time)
|
|
278
|
+
|
|
279
|
+
### Custo aparecendo como $0?
|
|
280
|
+
|
|
281
|
+
- Normal no MVP (estimativa simplificada, reconciliação depois)
|
|
282
|
+
- Operações muito baratas podem ficar abaixo do centavo
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Próximos Passos
|
|
287
|
+
|
|
288
|
+
- **Exemplo funcional:** Veja `example-checkout-app` no repo
|
|
289
|
+
- **Mais Serviços:** Lambda, SQS, SNS; Azure Functions, Service Bus (roadmap)
|
|
290
|
+
- **OpenTelemetry:** Rastrear custom operations (futuro)
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Suporte
|
|
295
|
+
|
|
296
|
+
Dúvidas? Abra issue no [GitHub](https://github.com/cloudbroker-app/cloudbroker-sdk).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# AWS Configuration
|
|
2
|
+
AWS_REGION=us-east-2
|
|
3
|
+
AWS_ACCESS_KEY_ID=your-access-key
|
|
4
|
+
AWS_SECRET_ACCESS_KEY=your-secret-key
|
|
5
|
+
|
|
6
|
+
# CloudBroker Configuration
|
|
7
|
+
CLOUDBROKER_API_KEY=sk_1234...
|
|
8
|
+
CLOUDBROKER_PRODUCT=checkout-example
|
|
9
|
+
CLOUDBROKER_TEAM=platform
|
|
10
|
+
|
|
11
|
+
# App Configuration
|
|
12
|
+
PORT=3000
|
|
13
|
+
NODE_ENV=development
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# CloudBroker SDK Example — Checkout Service
|
|
2
|
+
|
|
3
|
+
Exemplo funcional de como instrumentar um serviço de checkout com CloudBroker SDK para rastrear custo por feature.
|
|
4
|
+
|
|
5
|
+
## O Que Faz
|
|
6
|
+
|
|
7
|
+
Este app de exemplo mostra 5 features sendo rastreadas:
|
|
8
|
+
|
|
9
|
+
| Feature | Route | Operation | Label |
|
|
10
|
+
|---------|-------|-----------|-------|
|
|
11
|
+
| **Order Processing** | POST /orders | DynamoDB PutItem | `order-processing` |
|
|
12
|
+
| **Invoice Generation** | POST /orders | S3 PutObject | `invoice-generation` |
|
|
13
|
+
| **Order Retrieval** | GET /orders/:id | DynamoDB GetItem | `order-retrieval` |
|
|
14
|
+
| **Fraud Detection** | POST /verify-fraud | DynamoDB GetItem | `fraud-detection` |
|
|
15
|
+
| **Analytics Tracking** | POST /analytics | DynamoDB PutItem | `analytics-tracking` |
|
|
16
|
+
|
|
17
|
+
Cada feature rastreia seu custo AWS separadamente — depois você pode ver no dashboard CloudBroker quanto cada uma custou.
|
|
18
|
+
|
|
19
|
+
## Setup (5 minutos)
|
|
20
|
+
|
|
21
|
+
### 1. Instalar Dependências
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2. Configurar Credenciais
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cp .env.example .env
|
|
31
|
+
# Editar .env com suas credenciais AWS + CloudBroker API Key
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Campos obrigatórios:**
|
|
35
|
+
- `AWS_ACCESS_KEY_ID` — sua chave AWS
|
|
36
|
+
- `AWS_SECRET_ACCESS_KEY` — seu secret AWS
|
|
37
|
+
- `CLOUDBROKER_API_KEY` — sua API key CloudBroker (obter em app.cloudbroker.app.br)
|
|
38
|
+
|
|
39
|
+
### 3. Rodar
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm start
|
|
43
|
+
# Saída: ✅ Checkout Example App running on http://localhost:3000
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 4. Testar
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Criar pedido (rastreia 2 features)
|
|
50
|
+
curl -X POST http://localhost:3000/orders \
|
|
51
|
+
-H "Content-Type: application/json" \
|
|
52
|
+
-d '{
|
|
53
|
+
"customerId": "cust-123",
|
|
54
|
+
"items": [{"name": "Widget", "price": 99.99}],
|
|
55
|
+
"total": 99.99
|
|
56
|
+
}'
|
|
57
|
+
|
|
58
|
+
# Resposta:
|
|
59
|
+
# {
|
|
60
|
+
# "ok": true,
|
|
61
|
+
# "orderId": "order-1719600600000",
|
|
62
|
+
# "message": "Order created successfully!..."
|
|
63
|
+
# }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 5. Ver Custos
|
|
67
|
+
|
|
68
|
+
1. Acesse https://app.cloudbroker.app.br
|
|
69
|
+
2. Vá a **Custo por Código**
|
|
70
|
+
3. Você verá os labels: `order-processing`, `invoice-generation`, etc
|
|
71
|
+
4. **Nota:** Demora ~5 minutos pro CloudBroker processar os dados
|
|
72
|
+
|
|
73
|
+
## Exemplos de Uso
|
|
74
|
+
|
|
75
|
+
### Criar Pedido
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
curl -X POST http://localhost:3000/orders \
|
|
79
|
+
-H "Content-Type: application/json" \
|
|
80
|
+
-d '{
|
|
81
|
+
"customerId": "user-456",
|
|
82
|
+
"items": [{"name": "Widget A"}, {"name": "Widget B"}],
|
|
83
|
+
"total": 250.00
|
|
84
|
+
}'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**O que acontece:**
|
|
88
|
+
1. `order-processing`: DynamoDB registra o pedido
|
|
89
|
+
2. `invoice-generation`: S3 armazena o JSON da invoice
|
|
90
|
+
|
|
91
|
+
### Recuperar Pedido
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
curl -X GET http://localhost:3000/orders/order-1719600600000
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**O que acontece:**
|
|
98
|
+
1. `order-retrieval`: DynamoDB busca o pedido
|
|
99
|
+
2. CloudBroker rastreia o custo dessa operação
|
|
100
|
+
|
|
101
|
+
### Verificar Fraude
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
curl -X POST http://localhost:3000/verify-fraud \
|
|
105
|
+
-H "Content-Type: application/json" \
|
|
106
|
+
-d '{
|
|
107
|
+
"orderId": "order-123",
|
|
108
|
+
"customerId": "user-456"
|
|
109
|
+
}'
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**O que acontece:**
|
|
113
|
+
1. `fraud-detection`: DynamoDB consulta perfil do cliente
|
|
114
|
+
2. Retorna score de fraude (0–1)
|
|
115
|
+
|
|
116
|
+
### Registrar Evento de Analytics
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
curl -X POST http://localhost:3000/analytics \
|
|
120
|
+
-H "Content-Type: application/json" \
|
|
121
|
+
-d '{
|
|
122
|
+
"userId": "user-789",
|
|
123
|
+
"eventType": "page_view",
|
|
124
|
+
"data": {"page": "/checkout"}
|
|
125
|
+
}'
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**O que acontece:**
|
|
129
|
+
1. `analytics-tracking`: DynamoDB armazena o evento
|
|
130
|
+
|
|
131
|
+
## Entendendo os Custos
|
|
132
|
+
|
|
133
|
+
Depois de ~5 minutos, no dashboard CloudBroker você verá algo assim:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Feature: order-processing
|
|
137
|
+
Requests: 2
|
|
138
|
+
Total Cost: $0.000003
|
|
139
|
+
Cost per Request: $0.0000015
|
|
140
|
+
|
|
141
|
+
Feature: invoice-generation
|
|
142
|
+
Requests: 2
|
|
143
|
+
Total Cost: $0.000002
|
|
144
|
+
Cost per Request: $0.000001
|
|
145
|
+
|
|
146
|
+
Feature: order-retrieval
|
|
147
|
+
Requests: 1
|
|
148
|
+
Total Cost: $0.00000025
|
|
149
|
+
Cost per Request: $0.00000025
|
|
150
|
+
|
|
151
|
+
Feature: fraud-detection
|
|
152
|
+
Requests: 1
|
|
153
|
+
Total Cost: $0.00000025
|
|
154
|
+
Cost per Request: $0.00000025
|
|
155
|
+
|
|
156
|
+
Feature: analytics-tracking
|
|
157
|
+
Requests: 2
|
|
158
|
+
Total Cost: $0.000003
|
|
159
|
+
Cost per Request: $0.0000015
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Modo Desenvolvimento
|
|
163
|
+
|
|
164
|
+
Rodar com auto-reload:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
npm run dev
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Testes E2E
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
npm test
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Runs `test/e2e.js` que faz requisições e valida respostas.
|
|
177
|
+
|
|
178
|
+
## Troubleshooting
|
|
179
|
+
|
|
180
|
+
### "Nenhum dado aparecendo no dashboard"
|
|
181
|
+
|
|
182
|
+
1. ✅ API Key configurada?
|
|
183
|
+
```bash
|
|
184
|
+
echo $CLOUDBROKER_API_KEY
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
2. ✅ AWS credenciais válidas? (tente fazer um `aws s3 ls`)
|
|
188
|
+
|
|
189
|
+
3. ✅ Esperou 5+ minutos? (CloudBroker faz batch)
|
|
190
|
+
|
|
191
|
+
4. ✅ Fez pelo menos 1 request pro app?
|
|
192
|
+
```bash
|
|
193
|
+
curl http://localhost:3000/health
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### "Erro 403 na DynamoDB"
|
|
197
|
+
|
|
198
|
+
- Tabelas `CheckoutOrders`, `CustomerProfiles`, `AnalyticsEvents` não existem na sua conta AWS
|
|
199
|
+
- CloudBroker **não cria tabelas automaticamente** — você precisa criar:
|
|
200
|
+
```bash
|
|
201
|
+
aws dynamodb create-table \
|
|
202
|
+
--table-name CheckoutOrders \
|
|
203
|
+
--attribute-definitions AttributeName=orderId,AttributeType=S \
|
|
204
|
+
--key-schema AttributeName=orderId,KeyType=HASH \
|
|
205
|
+
--billing-mode PAY_PER_REQUEST
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### "Erro ao conectar no S3"
|
|
209
|
+
|
|
210
|
+
- Bucket `checkout-invoices` não existe
|
|
211
|
+
- Crie:
|
|
212
|
+
```bash
|
|
213
|
+
aws s3 mb s3://checkout-invoices-YOUR-UID --region us-east-2
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Próximos Passos
|
|
217
|
+
|
|
218
|
+
- Leia [docs/COST_PER_FEATURE.md](../docs/COST_PER_FEATURE.md) pro guia completo
|
|
219
|
+
- Copie os patterns aqui pro seu próprio backend
|
|
220
|
+
- Cada label representa uma feature — customize conforme suas necessidades
|
|
221
|
+
- Monitore no dashboard CloudBroker qual feature custa mais
|
|
222
|
+
|
|
223
|
+
## Suporte
|
|
224
|
+
|
|
225
|
+
Dúvidas? Abra issue em [github.com/rmoura151179/cloudbroker-sdk](https://github.com/rmoura151179/cloudbroker-sdk).
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cloudbroker-sdk-example-checkout",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Exemplo: Rastrear custo por feature com CloudBroker SDK",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "server.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node server.js",
|
|
9
|
+
"dev": "node --watch server.js",
|
|
10
|
+
"test": "node test/e2e.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["cloudbroker", "finops", "cost-tracking"],
|
|
13
|
+
"author": "CloudBroker",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"express": "^4.18.2",
|
|
17
|
+
"@aws-sdk/client-s3": "^3.400.0",
|
|
18
|
+
"@aws-sdk/client-dynamodb": "^3.400.0",
|
|
19
|
+
"@aws-sdk/lib-dynamodb": "^3.400.0",
|
|
20
|
+
"cloudbroker-sdk": "^0.5.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"nodemon": "^3.0.1"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18.0.0"
|
|
27
|
+
}
|
|
28
|
+
}
|