Fallstudie
Dredge: Ett operativt informationskort med information från flera källor
Skala
~7,300
Rader i biblioteket ”Elixir” (lib)
64
Moduler
4
Källadaptrar
14
Ecto migrationer
3
Anthropic klientlägen
~1,180
Testserier
Problemet
Små utvecklingsteam övervakar sin verksamhet genom sex flikar samtidigt: aviseringar från GitHub, varningar från Dependabot, en databas de måste hålla koll på, säkerhetsmeddelanden från RSS samt en ström av webhook-meddelanden från Sentry, Datadog och PagerDuty. Ingenting hänger ihop, ingenting prioriteras, och prioriteringen sker genom att man byter mellan olika uppgifter tills något faller mellan stolarna.
Dredge samlar allt detta i en enda kanban-tavla i realtid. Uppgifter strömmar in från alla anslutna källor, fördelas automatiskt i kolumner enligt fastställda regler och visas i prioritetsordning – vilket gör att teamet kan prioritera uppgifterna på ett och samma ställe istället för att behöva växla mellan sex olika flikar. Det var också, helt öppet, ett sätt att lära sig Elixir, OTP och Phoenix LiveView på rätt sätt, istället för genom en vanlig handledning.
Vad som byggdes
Källor (adapterlagret)
- ·RSS / Atom – omröstningsbaserad, med automatisk upptäckt av flödes-URL; anpassad för CVE och leverantörers säkerhetsflöden
- ·GitHub - OAuth; aviseringar, tilldelade ärenden, commit och varningar från Dependabot kopplade till kortets prioritet via CVSS
- ·Databas – en generisk, SELECT-only-baserad övervakningsmodul för Postgres och MySQL som markerar misstänkta rader och automatiskt identifierar kolumnerna för rubrik och allvarlighetsgrad
- ·Webhook – push-baserad, verifierad via HMAC – POST /webhooks/:id; stöder dataformat från GitHub, Sentry, Datadog och PagerDuty
- ·Varje källa implementerar ett visst beteende (fetch/3 + normalize/1), så pipelinen behöver aldrig veta vilken typ av källa den kommunicerar med
Styrelse och klassificering
- ·Regelbaserad vidarebefordran: skicka poster till kolumner utifrån fält/operator/värde, med en valfri prioriteringsfaktor (CVSS >= 9 ger prioritet 10)
- ·Klassificeringsalgoritm som väljer det första träffade alternativet med fallback till ”Inbox”; omklassificering av hela brädet när reglerna ändras
Skiff (AI-lagret)
- ·Konfiguration av AI-pipeline – beskriv vad du vill uppnå på ett enkelt och begripligt sätt; Skiff returnerar en validerad konfiguration av källkod och routningsregler som du kan förhandsgranska och godkänna innan något skrivs ut
- ·AI-brädeassistent – en agentbaserad verktygsanvändningsslinga som besvarar frågor om hela brädet, kan skapa kort och på begäran köra en skrivskyddad SQL-fråga mot en ansluten databas
- ·Sammanfattningar av kolumner som publiceras via Anthropic Meddelanden API via SSE
Samarbete och realtid
- ·Bjud in medlemmar (läsare/redaktör), offentliga länkar med läsbehörighet, avatarer som visar närvaro i realtid samt kommentartrådar för varje kort
- ·Phoenix PubSub + Presence: tavlan uppdateras direkt när ett objekt läggs till, utan att klienten behöver skicka förfrågningar
Teknisk arkitektur
Vad jag lärde mig
Det här var ett projekt där inlärning stod i centrum, så det här är vad jag faktiskt lärde mig av teknikstacken – flera av dem på det hårda sättet, vilket syns i Git-historiken.
1. OTP omdefinierar feltolerans som ett strukturellt val, inte som felhantering
Med bakgrund från backend-system baserade på begäran/svar var min instinktiva reaktion på frågan ”vad händer om ett flöde är nere?” att använda try/catch och flaggor för omförsök. OTP lyfte svaret till en högre nivå: modellera varje källa som en egen övervakad process. Dredge kör en SourceWorker GenServer per källa under en användarspecifik DynamicSupervisor, registrerad via {user_id, source_id}. En instabil RSS-feed kan krascha och starta om på egen hand utan att påverka användarens GitHub-polling, och arbetaren är :transient så att ett rent stopp förblir stoppat medan en krasch startas om. Vid uppstart läser en root-supervisor varje aktiv källa från databasen och startar en arbetare för varje. Felisolering slutade vara något jag kodade runt och blev något som systemets utformning ger mig gratis.
2. Beteenden gör ”lägg till ytterligare en integration” till ett avslutat problem
I den första versionen skapades grenar av typen `source_type` genom pollningskoden. Detta blir värre för varje källa. Genom att ersätta detta med ett adapterbeteende – fyra återanrop, där fetch/3 och normalize/1 utgör kärnan – kommunicerar arbetaren med en abstrakt källa och skapar aldrig en ny gren. Att senare lägga till Reddit eller Hacker News innebär att man implementerar en modul, inte redigerar pipelinen. Det är den tydligaste vinsten jag har upplevt av att designa mot ett gränssnitt snarare än mot de fall jag råkade ha på dag ett.
3. Den hanterade infrastrukturen kan drabbas av fel som inte beskrivs i dokumentationen
Det som tog mest tid var att få databasadaptern att kommunicera med Supabase, och det var inte alls Elixir:s fel. Tre verkliga hinder, alla i git-historiken: Supabases transaktionspooler avvisar namngivna förberedda satser (fixat genom att tvinga prepare: :unnamed på Postgrex), den "direkta" anslutningen är endast IPv6 och oåtkomlig från de flesta servrar (fixat genom att styra användare till IPv4-poolern), och postgres://-URL:er måste delas upp i separata värd/port/användare/lösenord-alternativ eftersom drivrutinen inte accepterar en URL. Den viktigaste åtgärden var inte alls kod – det var att skriva in anvisningarna för anslutningssträngen i installationsgränssnittet så att nästa person inte stöter på samma problem. Hälften av integrationsarbetet består i att omvandla din egen felsökning till instruktioner för någon annan.
4. En AI-funktion kräver en tillitsmodell och en kostnadsmodell redan från första början
Två saker som jag skulle ha lagt till i efterhand om jag inte hade tvingats fundera på dem. Säkerhet: assistenten kan göra sökningar i en ansluten databas, så sökvägen säkerställer strikt att SELECT-only används vid adaptern, begränsar antalet rader och anslutningssträngen förvaras krypterad – modellen får läsbehörighet, aldrig skrivbehörighet. Samma instinkt visar sig i pipeline-generatorn: AI-genererad konfiguration behandlas som ett icke-betrott förslag, visas för granskning och bekräftas först när användaren godkänner det, skrivs aldrig direkt till databasen. Kostnad: en delad API-nyckel är en delad faktura, så användningen går via en kvotlösare – administratörer har obegränsad tillgång, en användare som tillhandahåller sin egen Anthropic-nyckel har obegränsad tillgång på egen bekostnad, och alla andra får en mjuk daglig gräns på den delade nyckeln, där den agentiska slingan med flera anrop räknas som en begäran. Att integrera en LLM i en produkt handlar lika mycket om vad den inte kan göra och vad den inte får kosta som om vad den kan göra.
5. LiveView ger dig realtidsfunktioner utan något frontend-ramverk
Anslagstavlan uppdateras i realtid – nya inlägg, närvaroavatarer, kommentarer – utan någon ”React”, utan lagring på klientsidan och utan polling. Phoenix PubSub sänder ett ”:new_item” och servern renderar om det berörda fragmentet via WebSocket. Den disciplin det kräver är LiveView-strömmar för samlingar (så att en långlivad tavla inte slukar serverns minne) och att vara noggrann med vilken status som finns kvar på socket. Att byta ut SPA mot serverdriven rendering tog bort en hel kategori av buggar i synkroniseringen av klient-/serverstatus som jag är van vid att kämpa med.
6. Namnet på AI-systemet förändrade hur produkten uppfattas
En liten förändring med stor effekt. Assistenten hette ursprungligen ”Claude” i användargränssnittet och i meddelandena. Genom att byta namn till Skiff – och därmed ge den en produktidentitet istället för ett leverantörsnamn – framstod den som en funktion i Dredge snarare än en påklistrad chattbot, och på så sätt kopplades produktens röst diskret bort från den modell som ligger bakom den. Varumärkesförändringen och arkitekturförändringen (att behålla Anthropic-klienten bakom en modul) visade sig vara samma instinkt.
Säkerhet
- ·Inloggningsuppgifter och databasanslutningssträngar lagras krypterade i viloläge med hjälp av cloak_ecto
- ·Webhook-inläsningen verifieras av HMAC för varje källa innan någon datamängd godkänns
- ·Både AI-databasverktyget och databasadaptern tillämpar strikt principen om ”SELECT-only”; i installationsanvisningarna rekommenderas att man använder databasanvändare med skrivskyddade behörigheter
- ·Användningen av AI är begränsad till en viss kvot per användare för att skydda den delade nyckeln API