Drugie źródło migracji w naszym wdrożeniu Tier-3 zostało dzisiaj uruchomione. Wklej klucz API Rebrandly, opcjonalnie przefiltruj według obszaru roboczego, kliknij Start. Sześć do dziesięciu minut później każdy slashtag znajduje się w Twojej domenie Elido, z zachowanym slugiem, o ile nie wystąpiła kolizja. Migracja z Bitly, która pojawiła się dwa tygodnie temu, stworzyła fundamenty; Rebrandly to drugi dostawca, który z nich korzysta.
Ten wpis to opracowanie techniczne — co jest specyficzne dla Rebrandly, co zachowaliśmy identycznie jak w przypadku workera Bitly oraz gdzie API Rebrandly wymusiło inną strukturę.
Co jest wspólne z Bitly#
Cała funkcjonalność od początku miała opierać się na jednej tabeli i jednym kontrakcie workera. Oba założenia się sprawdziły.
CREATE TABLE import_jobs (
id BIGSERIAL PRIMARY KEY,
workspace_id BIGINT NOT NULL,
source_vendor TEXT NOT NULL,
target_domain_id BIGINT NOT NULL,
status TEXT NOT NULL DEFAULT 'queued',
conflict_strategy TEXT NOT NULL DEFAULT 'suffix',
source_filter JSONB NOT NULL DEFAULT '{}'::jsonb,
-- counters + error_log + timestamps elided
);
source_vendor zmienia się na rebrandly. source_filter zawiera {workspace_id: "..."}, gdy użytkownik filtruje; {}, gdy chce uzyskać każdy link widoczny dla danego klucza. Wszystko inne — 30-minutowy budżet, limit 50 tys. linków, strategia konfliktów typu suffix/skip/fail, tag imported:rebrandly — jest identyczne jak w przypadku ścieżki Bitly.
Launcher w panelu (apps/web/src/app/dashboard/integrations/[id]/rebrandly-migration-launcher.tsx) jest strukturalnie kopią launchera Bitly z usuniętą listą rozwijaną grup — Rebrandly posiada obszary robocze (workspaces), a nie grupy, więc udostępniamy je jako opcjonalny filtr tekstowy, zamiast wypełnianej listy rozwijanej, ponieważ endpoint Workspaces jest nieautoryzowany i stronicowany, a typowy użytkownik ma ich co najwyżej dwa.
W czym API Rebrandly się różni#
Trzy rzeczy:
Rozmiar strony. Rebrandly ogranicza pojedynczą stronę do 25 linków. Bitly do 100. Zatem konto z 5000 linków, którego migracja zajmuje 4–8 minut w Bitly, zajmuje 6–10 minut w Rebrandly. Wąskim gardłem jest dostawca, a nie worker.
Stronicowanie. Rebrandly używa parametru zapytania last, który przyjmuje identyfikator ostatniego elementu z poprzedniej strony. Bitly zwraca URL pagination.next. Oba są w stylu kursorów; ten z Rebrandly jest po prostu nieco bardziej gadatliwy. Cała pętla ma sześć linii:
last := ""
for {
page, err := w.fetchPage(ctx, opts.Token, opts.WorkspaceID, last)
if err != nil { /* mark failed */ return }
if len(page) == 0 { break }
for _, link := range page {
// ... resolve slug, insert, update counters ...
}
last = page[len(page)-1].ID
}
Ufamy kursorowi. Jeśli Rebrandly zwróci ten sam identyfikator last dwa razy, zapętlimy się; 30-minutowy budżet ogranicza szkody.
Zakres obszarów roboczych. Klucz API Rebrandly widzi każdy link w każdym obszarze roboczym, do którego należy użytkownik. Jeśli posiadasz konto agencji z pięcioma obszarami roboczymi klientów, prawie na pewno chcesz importować je pojedynczo. Launcher udostępnia to jako opcjonalne pole tekstowe — wklej identyfikator obszaru roboczego z paska adresu URL Rebrandly lub pozostaw puste, aby zaimportować "wszystko, co widzi klucz".
Czego nie migrujemy#
Historia kliknięć. Dane o kliknięciach w Rebrandly są dostępne tylko w planie Premium i są prezentowane jako liczniki zagregowane na link, a nie jako zdarzenia pojedynczych kliknięć. Informujemy o tym ograniczeniu w każdym miejscu widocznym dla użytkownika — na stronie przepisu w panelu, na stronie docelowej /migrate-from/rebrandly, w interfejsie postępu importu oraz w sekcji FAQ. Nowe kliknięcia trafiają do analityki Elido od momentu przełączenia.
Szablony UTM w Rebrandly. Są one funkcją czasu prezentacji w Rebrandly i nie mają przejrzystego API do eksportu. Odbuduj je jako reguły kampanii Elido campaign rules — tag imported:rebrandly służy do masowego przypisywania.
Stylizacja kodów QR. Domyślny kod QR Elido jest generowany dla każdego zaimportowanego linku; niestandardowe projekty należy zastosować ponownie. Większość użytkowników używa masowego filtrowania po tagach, aby po fakcie przypisać domyślną nakładkę CTA Elido lub kampanię.
Obsługa tokenów#
Identyczna jak w Bitly. Token nigdy nie trafia na dysk:
bgCtx := context.WithoutCancel(r.Context())
go h.rebrandly.Run(bgCtx, job.ID, imports.RebrandlyJobOptions{
Token: req.Token,
WorkspaceID: req.WorkspaceID,
})
source_token_id pozostaje NULL. Tabela service_tokens z ADR-0036 jest przeznaczona dla integracji Tier-2 z wklejanymi tokenami (Mailchimp, Brevo, Klaviyo), gdzie cykliczne użycie uzasadnia trwałe przechowywanie. W przypadku jednorazowych migracji, przechowywanie wyłącznie w pamięci jest właściwym kompromisem operacyjnym — użytkownik wkleja token raz, worker wykonuje zadanie, token znika.
context.WithoutCancel (Go 1.21+) zachowuje wartości kontekstu — logger, identyfikatory śledzenia, czas zakończenia — ale usuwa sygnał anulowania, dzięki czemu worker działa dłużej niż żądanie HTTP, które go uruchomiło. To ten sam wzorzec, co w przypadku workera Bitly, i ten sam wzorzec, którego użyje każdy przyszły dostawca migracji.
Rozwiązywanie konfliktów#
Trzy strategie, identyczne jak w Bitly. Użytkownik wybiera je podczas uruchamiania zadania:
- suffix (domyślnie): przechodzenie przez
mylink-2,mylink-3, … do 50 kandydatów. Po przekroczeniu 50 traktujemy to jako problem strukturalny i zgłaszamy błąd. - skip: pozostaw istniejący link Elido bez zmian, zaloguj wiersz źródłowy, policz jako pominięty.
- fail: przerwij całe zadanie przy pierwszym konflikcie. Dla ścisłej semantyki 1:1.
Wyszukiwanie sluga to jeden odczyt indeksowany na wiersz:
func (w *RebrandlyWorker) resolveSlug(ctx context.Context, domainID int64, desired, strategy string) (string, error) {
if _, err := w.links.GetByDomainSlug(ctx, domainID, desired); err != nil {
if errors.Is(err, pgx.ErrNoRows) { return desired, nil }
return "", fmt.Errorf("slug lookup: %w", err)
}
// suffix/skip/fail branching identical to bitly.go
}
Płacimy dodatkowym odczytem na wiersz, ale otrzymujemy deterministyczne przechodzenie przez sufiksy i bardziej przyjazny komunikat o błędzie. Alternatywa — szukanie naruszenia unikalności w pgx i parsowanie nazwy ograniczenia z ciągu błędu — jest gorszym rozwiązaniem.
Co jest mierzalne#
Te same ustrukturyzowane logi zap co w Bitly. Obszar roboczy, domena docelowa, strategia konfliktów, opcjonalny filtr obszaru roboczego. Zdarzenia cyklu życia zadania — start, zakończenie, wykrywanie zawieszonych zadań (stuck-sweep) — już istnieją, a panel sterowania odpytuje endpoint co dwie sekundy.
Nie tworzymy jeszcze wykresów metryk zadań migracyjnych na produkcji. Kohorta Bitly dała nam pierwszą bazę danych o rzeczywistym ruchu; dane z Rebrandly powinny być bezpośrednio porównywalne, ponieważ worker jest mechanicznie identyczny, a różnice dotyczą jedynie kształtu stronicowania dostawcy. Pierwszy kandydat do alertu: liczba zawieszonych zadań (stuck-sweep) > 0 w dowolnym oknie jednogodzinnym — oznacza to, że worker umarł, a interfejs użytkownika utknął w stanie running.
Wznawialność i problem wdrażania#
Ten sam kompromis co w Bitly. Worker działa wewnątrz procesu; wdrożenie w trakcie importu zabija gorutynę. Akceptujemy to w wersji v1, ponieważ:
- Większość zadań kończy się w mniej niż dziesięć minut. Wdrożenia są rzadkie w godzinach intensywnego importowania.
- Pole
import_jobs.last_progress_atoraz 5-minutowy cron wykrywający zawieszone zadania zmieniają status każdego wierszarunningbez postępu w ciągu ostatnich 30 minut nafailedz jasnym powodem. - Ponowne uruchomienie jest idempotentne w strategiach suffix i skip — już zaimportowane linki są wykrywane w drugim przebiegu i rozwiązywane zgodnie ze strategią.
W przypadku kont z ponad 10 000 linków, wznawialność staje się opłacalna — zapisujemy kursor last z Rebrandly w import_jobs.source_filter i wznawiamy pracę tam, gdzie ostatnie uruchomienie zostało przerwane. To kolejna iteracja; pozostałe cztery źródła migracji skorzystają z tej samej zmiany, gdy ją wdrożymy.
Co dalej#
Te same fundamenty, jeszcze trzech dostawców do wdrożenia w tej samej tabeli import_jobs.
- Short.io —
GET /links?limit=150&domain_id=…. Stronicowanie na domenę; prosimy użytkownika o wybranie domeny źródłowej zamiast obszaru roboczego. - Dub.co —
GET /api/links?projectSlug=…&limit=100. Folders + tags zachowane; to najczystsza implementacja z czterech. - TinyURL — Pro/Bulk REST API. Darmowy TinyURL nie posiada API i nigdy nie posiadał; ta ścieżka pozostaje ręczna.
Każdy z nich trafia za ten sam interfejs odpytywania w panelu sterowania i ten sam wzorzec tagu imported:<vendor>. Worker specyficzny dla dostawcy pozostaje w services/api-core/internal/imports/<vendor>.go.
Jeśli wstrzymywałeś się z porównaniem Rebrandly, ponieważ ścieżka migracji nie była udokumentowana, teraz już jest. Wypróbuj to — od klucza API do ostatniego zaimportowanego linku w mniej niż dziesięć minut w przypadku typowych kont.