Go SDK
Публичный API серверной библиотеки ActionPulse для Go — конфигурация, очередь, методы, типы ошибок и завершение процесса. Пакет не опубликован, рабочий путь сегодня — прямой HTTP.
На этой странице
В репозитории продукта есть серверная библиотека для Go. Она не опубликована, поэтому подключить её как зависимость нельзя. Эта страница показывает её настоящий публичный API — чтобы вы понимали, что вас ждёт, — и называет рабочий путь для бэкенда сегодня.
Версия в репозитории — 0.1.0, она общая для всех трёх серверных библиотек
(Go, Node.js, Python). Внешних зависимостей нет: только стандартная библиотека.
Чем SDK отличается от прямого HTTP
Прямой HTTP — это один POST и полный контроль. Библиотека добавляет к нему то,
что иначе пришлось бы написать самому:
| Что делает библиотека | Что это значит на практике |
|---|---|
| Проверяет контракт до отправки | ошибка в имени события видна как ошибка вызова, а не как rejected в ответе |
| Держит ограниченную очередь в памяти | всплеск событий не превращается в лавину запросов |
| Сама режет батчи | соблюдает 500 элементов и 256 КБ на запрос без вашего участия |
| Повторяет только то, что стоит повторять | ошибка транспорта, 429 и 5xx, с учётом Retry-After |
| Пересчитывает индексы отказов | rejected[i].I указывает на позицию в вашем исходном вызове, а не в очередном HTTP-запросе |
| Санирует ошибки | текст ошибки не содержит ключ, тело запроса, идентификаторы и свойства |
Чего библиотека не делает: она не запускает фоновых горутин и ничего не
отправляет сама. Пока вы не вызвали Flush или Close, события лежат в памяти
процесса.
Клиент
package main
import (
"context"
"log"
"os"
"time"
actionpulse "github.com/GoGerman/actionpulse/sdks/go"
)
func main() {
client, err := actionpulse.New(actionpulse.Config{
Endpoint: "https://YOUR_ACTIONPULSE_HOST",
WriteKey: os.Getenv("ACTIONPULSE_SERVER_KEY"),
AppVersion: "billing-2026.07.28",
BatchSize: 100,
})
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
defer func() {
if _, err := client.Close(ctx); err != nil {
log.Printf("actionpulse close failed: %v", err)
}
}()
if _, err := client.Track(actionpulse.Event{
Event: "order_paid",
UserID: "YOUR_USER_ID",
Time: time.Now().UTC().Format(time.RFC3339Nano),
SchemaVersion: 1,
Props: map[string]any{"order_id": "A-1024", "amount_minor": 99000},
}); err != nil {
log.Printf("actionpulse track failed: %v", err)
}
}
New проверяет конфигурацию и не делает сетевых запросов: неверный адрес
или пустой ключ вы увидите сразу при старте, а не при первой отправке.
Поля Config
| Поле | По умолчанию | Что важно знать |
|---|---|---|
Endpoint |
— | обязательно, абсолютный http/https адрес приёма |
WriteKey |
— | обязательно, серверный ключ pp_sk_…, без переводов строки |
HTTPClient |
&http.Client{} |
свой клиент, если нужны прокси или транспорт |
Timeout |
5s |
таймаут одной попытки |
QueueCapacity |
1000 |
предел очереди в памяти |
BatchSize |
100 |
элементов в одном запросе, допустимо от 1 до 500 |
MaxRetries |
3 |
допустимо от 1 до 10; -1 означает «без повторов», 0 даёт значение по умолчанию |
BaseBackoff |
100ms |
начальная задержка |
MaxBackoff |
2s |
потолок задержки, не меньше BaseBackoff |
AppVersion |
— | до 128 печатных ASCII-байт, уходит в заголовок диагностики |
Release |
— | то же, для идентификатора релиза |
Logger |
— | func(RetryLog), вызывается на каждой повторной попытке |
Processor |
— | func(Event) (*Event, error), последний фильтр перед очередью |
Sleep |
встроенный | своя реализация ожидания для тестов |
Поля Source и SchemaVersion объявлены устаревшими. Source игнорируется:
источник события выводится из типа ключа, а не из того, что указал вызывающий.
SchemaVersion в конфигурации работает как значение по умолчанию для
Event.SchemaVersion.
Processor вызывается для каждого события перед постановкой в очередь. Верните
изменённое событие или nil, чтобы его выбросить. EventID и Time
восстанавливаются после вызова, поэтому обработчик не может нарушить
идемпотентность. Обработчик должен быть безопасен для одновременных вызовов;
паника внутри превращается в ValidationError, а не роняет процесс.
Методы
| Метод | Подпись | Поведение |
|---|---|---|
QueueSize |
() int |
длина очереди |
Track |
(Event) (string, error) |
ставит одно событие в очередь, возвращает его event_id |
Batch |
([]Event) ([]string, error) |
проверяет все события и ставит их все или ни одного |
GroupEvent |
(Group) (string, error) |
ставит в очередь обычное событие group_identify |
Identify |
(ctx, Identity) error |
отправляет сразу, без очереди и без повторов |
Alias |
(ctx, Identity) error |
отправляет сразу; поле Traits игнорируется |
Flush |
(ctx) (TrackResponse, error) |
отправляет накопленное |
Close |
(ctx) (TrackResponse, error) |
отправляет накопленное и закрывает клиент |
Track и Batch возвращают event_id даже для событий, которые выбросил
Processor: значение уже зафиксировано и его можно сохранить у себя для
последующих повторов.
Flush возвращает агрегат по всем HTTP-запросам, которые понадобились, и
пересчитывает индексы отказов в порядок вашего вызова:
result, err := client.Flush(ctx)
if err != nil {
return err
}
for _, rejected := range result.Rejected {
log.Printf("event %d rejected: %s", rejected.I, rejected.Code)
}
TrackResponse — это Accepted, N и Rejected []Rejected{I, Code}, то же,
что приходит в теле ответа приёма. Событие удаляется из очереди после успешного
ответа, даже если оно отклонено по элементу: повтор такого события не поможет,
исправлять надо разметку.
Ошибки
| Тип | Когда | Что делать |
|---|---|---|
*ValidationError |
локальная проверка контракта | исправить вызов |
*QueueFullError |
очередь заполнена | увеличить QueueCapacity или вызывать Flush чаще |
*TransportError |
сеть или таймаут, повторы исчерпаны | повторить позже |
*APIError |
приём ответил не 202 |
смотреть Status и Code |
Разбирайте их через errors.As:
var apiErr *actionpulse.APIError
if errors.As(err, &apiErr) && apiErr.Status == 401 {
log.Println("actionpulse: credential rejected")
}
APIError содержит только Operation, Status и канонический Code из тела
ответа. Тела ответа, ключа и свойств события в тексте ошибки нет намеренно:
такая ошибка безопасна для журнала.
Logger получает RetryLog{Operation, Attempt, Status} — и ничего больше.
По умолчанию журналирование выключено.
Завершение процесса
Автоматической отправки при выходе нет. Незакрытый клиент теряет всё, что осталось в очереди.
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if _, err := client.Close(shutdownCtx); err != nil {
log.Printf("actionpulse close failed: %v", err)
}
Важные детали:
Closeсначала отправляет очередь, и только потом перестаёт принимать вызовы.- Если отправка не удалась, клиент остаётся открытым: можно починить связь и
повторить
Close. - Передавайте в
Closeсвежий контекст. Контекст запроса на момент остановки сервиса обычно уже отменён, и отправка не состоится. - Повторный
Closeна уже закрытом клиенте возвращает пустой результат без ошибки.
Рабочий путь сегодня
Пока библиотека не опубликована, отправляйте события прямым HTTP: это тот же контракт, те же лимиты и те же коды отказов. Готовый пример на Go есть в отправке событий с бэкенда — там же перечислены поля события, коды отказов и правила повторов.
Если вы захотите позже перейти на библиотеку, полезно заранее сделать две вещи:
складывать event_id вместе с бизнес-фактом и держать отправку за одним
интерфейсом в своём коде. Тогда замена транспорта не затронет вызовы.
Дальше: лимиты и квоты и доставка, повторы и дубли.