Bilde av PUS-logo

Kom i gang med Unleash

Av Steffen Hageland 13 Aug 2018

I vår fikk vi satt opp en unleash-server her på NAV, for å se om dette var noe som ville gi verdi for oss. Det var i første omgang noe vi så for oss at vi kunne ta i bruk for FO-prosjektet - men samtidig så vi for oss at dette var noe som kunne være nyttig for alle på NAV. Det ble derfor tatt en avgjørelse, sammen med AURA, om at vi skulle sette opp en felles unleash-server for alle prosjektene på NAV.

Denne serveren har nå vært stabil i produksjon noen måneder - og vært i bruk i flere applikasjoner tilknyttet FO. Og siden vi har hatt veldig gode erfaringer med bruk av unleash tenkte vi at det kunne være greit å prøve å oppfordre andre til å også ta denne i bruk.

I denne posten vil jeg derfor gi en introduksjon til hva unleash er og hvordan du kan ta det i bruk på ditt prosjekt. Vil og komme med en liste av best-practices som vi ønsker at folk skal følge. Så fint om man også ser over disse.

Hva er unleash?

Unleash er et verktøy for feature-toggling. Applikasjonen er utviklet av finn.no som et open-source prosjekt som ligger åpent på github. Målet med unleash er å være et feature-toggle-system som gir en god oversikt over statusen på ulike feature-toggles for ulike applikasjoner.

Det ligger en åpen online-demo på heroku hvor man kan få testet det ut.

Hvorfor burde jeg ta i bruk unleash?

Når man jobber med kontinuerlige leveranser kan det være et ønske å løsrive deploy fra lansering. Ved å ha nye features lagt bak en feature toggle får man muligheten til å produksjonssette kode uten å nødvendigvis lansere funksjonaliteten som er lagt til. Dette betyr at man kan produksjonssette uferdig og utestet kode uten å skape problemer. I tillegg åpner det opp for muligheten til å teste koden direkte i produksjon, ved for eksempal å kun aktivere den for testbrukere.

Siden lansering av nye features skjer runtime via et dashboard, og ikke ved deploy, betyr det at man har muligheten til å respondere ved eventuelle feil og deaktivere featuren uten å måtte kjøre ut en ny deploy eller rulle tilbake.

Man har og mulighet for å legge på ulike strategier for aktivering av en toggle. Som f.eks. gradvis utrulling hvor en feature er på for et bestemt antall prosent av brukere. Eller man kan ha en strategi som betyr at koden kun er aktiv for brukere som har registrert seg som villige betatestere. På den måten har man mulighet til å rulle ut en ny feature til en mindre brukergruppe først, og overvåke om det oppstår noen nye feil. Og hvis alt ser bra ut kan man velge og rulle ut featuren for alle brukerene.

Hvordan ta i bruk unleash

På NAV har vi allerede satt opp en Unleash-server som kjører på NAIS i fagsystemsonen. Denne er tilgjengelig via https://unleash.nais.adeo.no. Om man ønsker å nå denne fra selvbetjeningssonen kan man gå via en proxy vi har satt opp på https://unleashproxy.nais.oera.no.

Den letteste måten å ta dette i bruk er å legge til unleash-client som en avhengigheten i applikasjonen din. Det finnes implementasjoner for flere språk, blandt annet java og Go. Når pakken er importert må unleash konfigureres opp. I java gjøres dette på følgende måte:

UnleashConfig unleashConfig = UnleashConfig.builder()
    .appName("min-app")
    .instanceId(getEnvironment())
    .unleashAPI("https://unleash.nais.adeo.no/api")
    .build();

Unleash unleash = new DefaultUnleash(unleashConfig);

InstanceID er ikke selvforklarende, se på den som en identifikator for ulike instanser av samme applikasjon. I FO bruker vi dette for å skille ulike miljøer fra hverandre. Altså vil pre-prod og prod være ulike instanser, men ulike pods i samme miljø vil bruke samme instans-id. Men her er det litt opp til dere, og hva som passer deres behov.

Om man da ønsker å sjekke status på en feature (på eller av) kan man bare kalle Unleash-klienten med navnet på feature toggle-en.

unleash.isEnabled("min-feature");

Når man gjør dette vil Unleash sjekke opp statusen på feature toggle-en, kjøre en sjekk mot valgte strategier og returnere om feature toggle-en skal være aktivert eller ikke. Kommunikasjon mellom klienten og serveren skjer asynkront, og klienten cacher statusen på alle feature toggles. Dette betyr at det ikke vil være noen vesentlig forsinkelse av å spørre omstatus på en feature toggle. Men siden oppdatering av status skjer asynkront (default er cirka hvert 30. sekund) vil det være en kort forsinkelse fra man gjør endringer på en feature toggle til endringen er synlig.

Nå som man har lagt til feature toggle-en i applikasjonen kan man gå inn på dashboardet på https://unleash.nais.adeo.no. Her vil du bli bedt om å logge inn. Applikasjonen er koblet mot Azure AD, så man skal kunne logge inn med nav-eposten. Om du ikke får tilgang betyr dette at du trolig mangler riktig AD-rolle. I så fall kan du ta kontakt i kanalen #pus på slack.

Når du har logget inn vil du få opp et dashboard med oversikt over alle aktive feature toggles. For å få den opp den nye toggle-en her må vi opprette den. Dette kan vi gjøre ved å trykke på plusstegnet oppe i høyre hjørnet.

Når man oppretter en ny toggle blir man bedt om å skrive inn et navn, beskrivelse og legge til strategier. Navnet må være det samme som det du skrev i applikasjonen din. I tillegg er det greit med en god beskrivelse. Strategiene er det som bestemmer om en toggle skal være på eller av - vi kommer mer inn på dette i neste del. Det letteste i starten er å legge til strategien default for å enkelt skru av og på en feature.

Strategier

En strategi i unleash er logikk som gjør en vurdering av om en feature toggle skal være av eller på for et bestemt request. Basert på inputparametere fra dashboardet og en context skal strategien evaluere til enten true eller false. En feature toggle kan ha 1 eller flere strategier aktivert, og selve feature toggle-en er regnet på på hvis toggle-en er aktivert i dashboardet og minst 1 av strategiene returnerer true.

Unleash kommer med et sett ferdige strategier. Flere av disse baserer seg på context som vi vil komme inn på senere. I tillegg har man mulighet til å opprette egne strategier, om det er ønskelig. Her er et utvalg av ferdige strategier:

Å opprette nye strategier gjøres ved å gå inn i “strategies” i menyen og trykke på plusstegnet for å legge til ny strategi. Her legger man inn et navn, beskrivelse og legge ved eventuelle parametere man ønsker til strategien. Det som er viktig å huske på at er dette kun legger til strategien i dashboardet - for at en strategi skal være i bruk må den implementeres i klienten. Dette er fordi det er klienten som står ansvarlig for å evaluere feature toggles og dermed også strategiene som er gjeldende for den bestemte feature toggle-en. Dette betyr også at ulike klienter kan implementere samme strategi på ulike måter, f.eks. om man har en strategi med navnet notProd f.eks. en klient definere dette som at strategien er true hvis for alle andre miljøer enn prod, mens en annen applikasjon ønsker at Q0 skal være prodlikt og dermed implementerer at denne er deaktivert i både prod og Q0.

For å implemenetere en ny strategi i en java-applikasjon lager man en ny klasse som implementerer Strategy og implementerer de ulike metodene på interfacet. F.eks. kan det være ønskelig å la en feature være av i produksjon men aktivert i staging-miljøet for testing. Dette kan man oppnå med å lage en strategi som returnerner true om miljøet ikke er produksjon.

class NotProdStrategy implements Strategy {
    @Override
    public String getName() {
        return "NotProd";
    }

    @Override
    public boolean isEnabled(Map<String, String> params) {
        return !getCurrentEnvironment().equals("production");
    }
}

Parameteret params som blir sendt inn til metoden her er et map av verdier på eventuelle parametere du konfigurerte i dashboardet. F.eks. for strategien userWithId ville eventuelle identer du konfigurerte strategien med bli lagt ved dette parameteret.

Når man har opprettet en ny strategi må man gi beskjed til Unleash om at denne strategien skal brukes. Dette kan enklest gjøres ved å legge ved strategien i konstruktøren til DefaultUnleash.

Unleash-context

Et viktig konsept i unleash er context som brukes for å si “i hvilken kontekst spør man om en feature er på”. I praksis betyr det ting som må bestemmes runtime. For backend-applikasjoner vil dette typisk være informasjon rundt request som ble gjort. For eksempel er identen på brukeren som gjør requesten en typisk ting som blir lagt ved requesten. Denne kan så brukes for å evaluere om en feature toggle skal være av eller på i contexten av en bestemt request.

Et eksempel kunne være at man ønsket å ha en feature som kun var aktivert for beta-brukere. Altså brukere som har registrert seg som villige til å ta i bruk beta-features på tross av at det kan være noe ustabilt. I vårt eksempel kan vi se for oss at vi har lagret et flagg i databasen for om brukeren ønsker beta-features eller ikke.

Vi starter med å implementere strategien. Her setter vi den vanlige isEnabled til å returnere false - siden vi da ikke har nok informasjon til å evaluere om den skal være på eller av - og defaulter da til av. I tillegg til denne finnes det og en overloadet versjon av isEnabled som tar inn 2 parametere, hvor det andre parameteret er UnleashContext. Unleash-context er et objekt som inneholder verdier som userId, sessionId, remoteAddr. I tillegg har den et map med ekstra custom-verdier som man kan bruke om ønskelig.

class BetaUserStrategy implements Strategy {
    @Override
    public String getName() {
        return "BetaUser";
    }

    @Override
    public boolean isEnabled(Map<String, String> params) {
        return false;
    }

    @Override
    public boolean isEnabled(Map<String, String> params, UnleashContext unleashContext) {
        return unleashContext.getProperties()
                .getOrDefault("isBetaUser", "false")
                .equalsIgnoreCase("true");
    }
}

I koden er feature toggle-en aktivert om felter isBetaUser er satt til true på contexten. Det neste er da å sørge for at man får sendt inn en context med dette feltet til strategien. For å få til dette finnes det i hovedsak 2 måter:

Sende inn context manuelt til isEnabled

UnleashContext context = UnleashContext.builder()
        .addProperty("isBetaUser", repository.getIsBetaUser(RequestUtils.getUserId()))
        .build();
unleash.isEnabled("someToggle", unleashContext);

Lage en UnleashContextProvider

Dette er i mange tilfeller den anbefalte måten i spring-applikasjoner. Da kan man lage en context-provier som vil brukes når man sjekker status på alle toggles og slipper å selv legge den ved. I Spring-verdenen vil dette typisk være en “request-scoped” bønne.

@Component
public class MyContextProvider implements UnleashContextProvider {
    @Inject
    private UserRepository userRepository;
    
    @Override
    public UnleashContext getContext() {
        return UnleashContext.builder()
                .addProperty("isBetaUser", userRepository.isBetaUser(RequestUtils.getUserId()))
                .build();
    }
}

Unleash i frontend-applikasjoner

Et behov som ofte går igjen i NAV er at man ønsker å bruke feature toggles i frontend. Dette er en naturlig konsekvens av at features som treffer sluttbruker ofte er implementert i biblioteker som React. Utfordringen med Unleash er at det ikke finnes noe godt rammeverk for å hente ut feature toggles direkte fra frontend, og på grunn av måten man bruker strategier på kan man heller ikke på en enkel måte gå direkte mot API-et til Unleash og hente ut verdiene på ulike toggles.

Arkitekturskisse over Unleash i en frontendapplikasjon

Det vi har erfart på ulike prosjekter på FO er at det enkleste er å la backenden ta ansvaret for feature toggles og heller ha et eget endepunkt hvor man eksponerer ut statusen på feature toggles til frontenden. For vår del har vi valgt å lage et REST-endepunkt som tar inn en liste med features og returnerer statusen på alle featurene tilbake. På den måten trenger frontenden kun å gjøre 1 kall for å hente ut status på alle feature toggelene den har behov for.

Best practices

Siden unleash-serveren blir delt av alle på NAV er det fint om man prøver å holde den litt ryddig og prøver å ha noen felles konvensjoner som alle følger. Dette vil gjøre det lettere for alle å holde oversikt.