К содержанию
ActionPulse

Go SDK

Публичный API серверной библиотеки ActionPulse для Go — конфигурация, очередь, методы, типы ошибок и завершение процесса. Пакет не опубликован, рабочий путь сегодня — прямой HTTP.

Кому
Go-разработчикам
Проверено
На этой странице

В репозитории продукта есть серверная библиотека для 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 вместе с бизнес-фактом и держать отправку за одним интерфейсом в своём коде. Тогда замена транспорта не затронет вызовы.

Дальше: лимиты и квоты и доставка, повторы и дубли.