La quarta fonte di migrazione nel nostro rollout di Tier-3 è stata rilasciata oggi. Incolla un token API TinyURL Pro o Bulk, scegli un dominio Elido di destinazione, clicca su Avvia. Da quattro a sette minuti dopo, ogni alias TinyURL si trova sul tuo dominio Elido con l'alias preservato dove non c'erano collisioni.
Questo post è l'approfondimento ingegneristico — cosa c'è di specifico per TinyURL, il limite deliberato che abbiamo implementato e perché la "migrazione TinyURL dal piano gratuito" non è qualcosa che possiamo costruire.
Il problema del piano gratuito#
TinyURL pubblico non ha un'API e non l'ha mai avuta. Il classico tinyurl.com/<slug> che crei senza un account è un redirect fire-and-forget — l'utente lo crea tramite il modulo della homepage, ottiene uno slug e lo slug non riappare mai in nessuna dashboard di account. Non esiste un elenco per utente perché non esiste un legame per utente.
Questo è ben noto, ma vale la pena metterlo in evidenza sulla pagina di destinazione /migrate-from/tinyurl perché la query di ricerca "migrate from TinyURL" non distingue tra Pro e gratuito. Abbiamo implementato:
- Un chiaro richiamo "Solo Pro/Bulk" nell'hero della pagina di destinazione.
- Una voce nelle FAQ che indirizza gli utenti del piano gratuito al modulo /docs/guides/bulk-create per l'abbreviazione massiva incollando una lista di destinazioni.
- Un passaggio di convalida del token nel launcher che fallisce rapidamente con "questo token non è su un piano Pro o Bulk" invece di lasciare che l'esecuzione vada in 401 silenziosamente a metà paginazione.
Il ragionamento: ogni altra fonte di migrazione che rilasciamo ha un percorso felice per "ogni utente che la cerca". TinyURL è l'eccezione — gli utenti del piano gratuito hanno bisogno di un modello mentale diverso e dovremmo impostare tale aspettativa prima che incollino qualsiasi cosa.
Struttura dell'API REST Pro/Bulk#
L'API TinyURL Pro è diretta: bearer token, risposte JSON, 100 alias per pagina. La paginazione utilizza un parametro query-string page che è indicizzato a 1; la risposta include data.aliases (l'array dei link) e meta.has_more (il segnale di continuazione).
const tinyurlPageSize = 100
page := 1
for {
resp, err := w.fetchPage(ctx, opts.Token, page)
if err != nil { /* contrassegna come fallito */ return }
if len(resp.Data.Aliases) == 0 { break }
for _, alias := range resp.Data.Aliases { /* importa */ }
if !resp.Meta.HasMore { break }
page++
}
Ogni alias trasporta url (la destinazione lunga), alias (lo slug personalizzato o il codice breve auto-generato), description (un campo opzionale di TinyURL che preserviamo come titolo del link Elido) e un domain (TinyURL consente domini brandizzati sui piani Bulk).
Terminologia — alias vs slug#
TinyURL li chiama "alias". Noi li chiamiamo "slug". È la stessa cosa — la sequenza di caratteri dopo l'host nell'URL di reindirizzamento. La migrazione preserva l'alias 1:1 dove il dominio Elido di destinazione non ha collisioni; se c'è una collisione, si applica la strategia di conflitto standard suffisso/salta/fallisci.
Abbiamo considerato di rinominare "slug" in "alias" nel launcher per corrispondere alla terminologia del fornitore di origine e lo abbiamo rifiutato per ragioni di coerenza. Ogni altra interfaccia di Elido — lista dei link, API, SDK, dashboard — usa "slug". Importare l'asimmetria terminologica in un launcher renderebbe l'esperienza post-importazione confusa.
Il launcher inserisce un'etichetta di una riga che dice "TinyURL chiama questi alias" sopra il radio button della strategia di conflitto, così gli utenti che cercano "alias" nella pagina della ricetta trovano il controllo giusto senza leggere ogni parola.
Domini brandizzati e il passaggio DNS#
I piani TinyURL Bulk supportano domini brandizzati — il tuo hostname che instrada attraverso l'infrastruttura di TinyURL. Quando migri a Elido, lo slug viene importato pulito e il lato DNS è un cambio di CNAME.
Il caso interessante è "Ho un dominio brandizzato su TinyURL Bulk e voglio mantenere lo stesso hostname dopo la migrazione". Lo gestiamo allo stesso modo della migrazione Short.io:
- La migrazione viene completata. I link importati risiedono su
s.elido.me/<alias>per impostazione predefinita (o il tuo dominio personalizzato Elido esistente). - Aggiungi il dominio brandizzato TinyURL come dominio personalizzato Elido tramite /docs/guides/custom-domains.
- Punti il CNAME verso Elido. Il TLS on-demand di Caddy emette un certificato alla prima richiesta;
domain-managerè la fonte di verità per la whitelist, quindi gli hostname non autorizzati vengono rifiutati. - L'interfaccia di TinyURL smette di risolvere per quell'hostname; quella di Elido prende il sopravvento.
Puoi mantenere entrambe le interfacce attive in parallelo finché non termina il tuo abbonamento TinyURL, poi il passaggio finale è semplicemente lasciare scadere l'hostname TinyURL. Nessuna urgenza, nessun rischio nel giorno del passaggio.
Cosa non migriamo#
Storico dei clic. Le analisi di TinyURL Pro/Bulk sono endpoint di report separati che non sono strutturati per l'esportazione. Il piano Bulk espone i conteggi dei clic per link nella dashboard ma non li rende disponibili tramite l'API adatta alla migrazione; i nuovi clic atterrano nelle analisi di Elido dal momento del passaggio.
Stile QR e template UTM del piano Bulk. Stessa storia di ogni altra fonte di migrazione — lo slug viene importato, il layer di presentazione circostante va ricostruito in Elido. Contrassegnato con imported:tinyurl per il follow-up massivo tramite campagne.
Link TinyURL del piano gratuito. Come discusso sopra, TinyURL pubblico non ha un'API. La mitigazione è il modulo di creazione massiva, non un job di migrazione.
Gestione dei token#
Stessa semantica "one-shot" di Bitly, Rebrandly e Short.io:
bgCtx := context.WithoutCancel(r.Context())
go h.tinyurl.Run(bgCtx, job.ID, imports.TinyURLJobOptions{
Token: req.Token,
})
source_token_id rimane NULL. Il token vive nella memoria del processo api-core per l'esecuzione del worker e viene eliminato al completamento. Nessuna persistenza, nessuna riga service_tokens, nessuna crittografia envelope ADR-0036 — quelle sono per le integrazioni di Tier-2 dove l'utente vuole chiamate ricorrenti al fornitore.
Il passaggio di convalida del token all'avvio del job colpisce l'endpoint /account/domains di TinyURL — chiamata economica, restituisce una lista di domini che il token può vedere. Se restituisce 401, falliamo rapidamente con "token non valido o non su un piano Pro/Bulk" invece di lasciare che l'utente aspetti due minuti per un 401 a metà paginazione e un messaggio di errore meno utile.
Risoluzione dei conflitti#
Identica a ogni altro fornitore di migrazione — il suffisso esegue myalias-2, myalias-3, … in caso di collisione; salta lascia il link Elido esistente intatto e registra la riga di origine; fallisci interrompe al primo conflitto.
func (w *TinyURLWorker) 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("lookup slug: %w", err)
}
// branching suffisso/salta/fallisci identico a bitly.go
}
La ricerca è una lettura indicizzata per riga. Paghiamo una lettura extra ma otteniamo percorsi di suffissi deterministici e messaggi di errore più amichevoli rispetto al cercare violazioni di univocità.
Il contratto del worker#
MaxLinksPerImport=50_000, ImportRunBudget=30*time.Minute, progressEvery=50, errorLogCap=1_000. Condiviso tra tutti e cinque i fornitori di migrazione. Queste costanti sono il contratto che l'interfaccia UI di polling della dashboard assume.
Un account TinyURL Pro da 2.000 alias colpisce l'API 20 volte e finisce in 3–5 minuti. Un account Bulk da 20.000 alias richiede 200 round-trip e finisce in 15–20 minuti. Sopra i 50.000 alias il worker fallisce drasticamente con l'istruzione di inviare un'email a [email protected] per una migrazione suddivisa in blocchi; il percorso migrazione suddivisa è solo su richiesta nella v1.
Riprendibilità e il problema del deploy#
Stesso compromesso delle prime tre migrazioni. Il worker è in-process; un deploy a metà importazione uccide la goroutine. Il cron di "pulizia dei job bloccati" trasforma qualsiasi riga running senza progressi in 30 minuti in failed. Rieseguire è idempotente sotto suffisso e salta.
Per account con oltre 10.000 alias, la riprendibilità guadagnerebbe il suo valore — registreremmo il cursore di page di TinyURL in import_jobs.source_filter e riprenderemmo dall'ultima pagina completata. Gli altri quattro fornitori di migrazione beneficeranno dello stesso cambiamento una volta rilasciato; il design è condiviso.
Alternativa CSV#
Per gli utenti su piani Bulk con un CSV esportato che non hanno più un token API attivo, eseguiamo job CSV una tantum dalla posta in arrivo — invia un'email a [email protected]. Non abbiamo rilasciato un modulo di caricamento CSV self-service perché il percorso REST copre il caso comune e il percorso CSV necessita di una manipolazione dello schema per account che è meglio fare a mano piuttosto che con un parser generico fragile.
Cosa c'è dopo#
Ancora un fornitore da implementare:
- Dub.co —
GET /api/links?projectSlug=…&limit=100. Le cartelle vengono appiattite in tag. L'API più pulita delle cinque.
Dopo Dub, il rollout di Tier-3 è completato. Cinque sbarchi di migrazione, cinque post tecnici sul blog, un'impalcatura di worker condivisa, una UI di polling della dashboard condivisa.
Se hai aspettato perché la migrazione TinyURL non era documentata, ora lo è. Provala — dal token Pro/Bulk all'ultimo link importato in meno di sette minuti per gli account tipici.