Webudvikling er en af de mest populære use cases til programmering. Python er et af de mest populære programmeringssprog i verden. Så hvorfor kan vi ikke bygge webapps i Python?
Det skal være enkelt at lave en brugergrænseflade, men selv du har gode ingeniører på dit team, var det at lære et nyt sprog og nye værktøjer en enorm barriere. Ofte kan det være sværere at lave en brugergrænseflade end det faktiske arbejde, man udfører! Vi byggede Reflex, en open source Python-webramme for at løse netop dette problem.
Under hætten kompilerer Reflex-apps til en React- frontend-app og en FastAPI- backend-app. Kun brugergrænsefladen er kompileret til Javascript; al applogik og tilstandsstyring forbliver i Python og køres på serveren. Reflex bruger WebSockets til at sende hændelser fra frontend til backend og til at sende tilstandsopdateringer fra backend til frontend.
Der var allerede et par måder at bygge apps i Python på, men ingen af dem passede til vores behov.
På den ene side er der rammer som Django og Flask , der er gode til at bygge produktions-grade web-apps. Men de håndterer kun backend - du skal stadig bruge JavaScript og en frontend framework, samt skrive en masse boilerplate kode for at forbinde frontend og backend.
På den anden side kan rene Python-biblioteker som Dash og Streamlit være gode til små projekter, men de er begrænset til en specifik use case og har ikke funktionerne og ydeevnen til at bygge en fuld webapp. Efterhånden som din app vokser i funktioner og kompleksitet, kan du finde på at ramme rammens grænser, hvorefter du enten skal begrænse din idé, så den passer til rammeværket, eller skrotte dit projekt og genopbygge det ved hjælp af en "rigtig webramme".
Vi ønsker at bygge bro over dette hul ved at skabe en ramme, der er nem og intuitiv at komme i gang med, samtidig med at den forbliver fleksibel og kraftfuld til at understøtte enhver app.
Lad os nu dykke ned i, hvordan vi byggede Reflex for at nå disse mål.
Full-stack webapps består af en frontend og en backend. Frontenden er brugergrænsefladen, og serveres som en webside, der kører på brugerens browser. Backend'en håndterer logikken og tilstandsstyringen (såsom databaser og API'er) og køres på en server. I traditionel webudvikling er disse normalt to separate apps, og de er ofte skrevet i forskellige rammer eller sprog. For eksempel kan du kombinere en Flask-backend med en React-frontend. Med denne tilgang skal du vedligeholde to separate apps og ende med at skrive en masse boilerplate-kode for at forbinde frontend og backend.
Vi ønsker at forenkle denne proces i Reflex ved at definere både frontend og backend i en enkelt kodebase, mens vi bruger Python til alt. Udviklere bør kun bekymre sig om deres apps logik og ikke om implementeringsdetaljerne på lavt niveau.
Vi ønsker, at Reflex-apps skal se ud og føles som en traditionel web-app for slutbrugeren, mens de stadig er nemme at bygge og vedligeholde for udvikleren. For at gøre dette byggede vi oven på modne og populære webteknologier.
Når du reflex run
din app, kompilerer Reflex frontenden ned til en enkeltsidet Next.js -app og serverer den på en port (som standard 3000
), som du kan få adgang til i din browser.
Frontendens opgave er at afspejle appens tilstand og sende hændelser til backend, når brugeren interagerer med brugergrænsefladen. Der køres ingen egentlig logik på frontend.
Reflex frontends er bygget ved hjælp af komponenter, der kan sammensættes for at skabe komplekse brugergrænseflader. I stedet for at bruge et skabelonsprog, der blander HTML og Python, bruger vi bare Python-funktioner til at definere brugergrænsefladen.
Under hætten samles komponenter ned til React-komponenter. Mange af vores kernekomponenter er baseret på Radix , et populært React-komponentbibliotek. Vi har også mange andre komponenter til graftegning, datatabeller og meget mere. Vi valgte React, fordi det er et populært bibliotek med et enormt økosystem. Vores mål er ikke at genskabe webøkosystemet, men at gøre det tilgængeligt for Python-udviklere.
Dette lader også vores brugere medbringe deres egne komponenter, hvis vi ikke har en komponent, de har brug for. Brugere kan indpakke deres egne React-komponenter og derefter udgive dem, så andre kan bruge dem. Over tid vil vi bygge vores tredjeparts komponentøkosystem ud, så brugerne nemt kan finde og bruge komponenter, som andre har bygget.
Vi ønskede at sikre os, at Reflex-apps ser godt ud, mens de stadig giver udviklere fuld kontrol over udseendet af deres app.
Vi har et kernetemasystem, der lader dig indstille stylingmuligheder på højt niveau, såsom mørk tilstand og accentfarve i hele din app for at give den et ensartet udseende og følelse.
Ud over dette kan Reflex-komponenter styles ved hjælp af CSS's fulde kraft. Vi udnytter Emotion- biblioteket til at tillade "CSS-in-Python"-styling, så du kan overføre enhver CSS-rekvisit som et nøgleordsargument til en komponent. Dette inkluderer responsive rekvisitter ved at sende en liste over værdier.
I Reflex kompilerer kun frontenden til Javascript og kører på brugerens browser, mens al tilstand og logik forbliver i Python og køres på serveren. Når du reflex run
, starter vi en FastAPI-server (som standard på port 8000
), som frontenden opretter forbindelse til via en websocket.
Al tilstand og logik er defineret inden for en State
. Staten består af vars og hændelseshandlere . Vars er alle værdier i din app, der kan ændre sig over tid. De er defineret som klasseattributter på din State
klasse og kan være en hvilken som helst Python-type, der kan serialiseres til JSON.
Hændelseshandlere er metoder i din State
klasse, der kaldes, når brugeren interagerer med brugergrænsefladen. De er den eneste måde, hvorpå vi kan ændre vars i Reflex, og kan kaldes som svar på brugerhandlinger, såsom at klikke på en knap eller skrive i en tekstboks.
Da hændelseshandlere køres på backend, kan du bruge et hvilket som helst Python-bibliotek i dem.
Normalt, når du skriver webapps, skal du skrive en masse boilerplate-kode for at forbinde frontend og backend. Med Reflex behøver du ikke bekymre dig om det – vi klarer kommunikationen mellem frontend og backend for dig. Udviklere skal bare skrive deres hændelseshåndteringslogik, og når vars er opdateret, opdateres brugergrænsefladen automatisk.
Brugeren kan interagere med brugergrænsefladen på mange måder, såsom at klikke på en knap, skrive i en tekstboks eller holde musen over et element. I Reflex kalder vi disse hændelsestriggere .
På frontenden opretholder vi en begivenhedskø med alle afventende begivenheder. En hændelse består af tre store datastykker:
Når en hændelse udløses, tilføjes den til køen. Vi har et processing
for at sikre, at kun én begivenhed behandles ad gangen. Dette sikrer, at tilstanden altid er konsistent, og at der ikke er nogen løbsforhold med to hændelseshandlere, der ændrer tilstanden på samme tid. Der er undtagelser til dette, såsom baggrundsbegivenheder, som giver dig mulighed for at køre begivenheder i baggrunden uden at blokere brugergrænsefladen.
Når begivenheden er klar til at blive behandlet, sendes den til backend via en WebSocket-forbindelse.
Når begivenheden er modtaget, behandles den på backend. Reflex bruger en tilstandsadministrator , som vedligeholder en kortlægning mellem klienttokens og deres tilstand. Som standard er tilstandsadministratoren kun en ordbog i hukommelsen, men den kan udvides til at bruge en database eller cache. I produktionen bruger vi Redis som vores statschef.
Når vi har brugerens tilstand, er næste trin at køre hændelseshandleren med argumenterne.
Hver gang en hændelseshandler vender tilbage (eller giver efter), gemmer vi tilstanden i tilstandsadministratoren og sender tilstandsopdateringerne til frontenden for at opdatere brugergrænsefladen. For at opretholde ydeevnen, efterhånden som din tilstand vokser, holder Reflex internt styr på vars, der blev opdateret under hændelseshåndteringen ( dirty vars ).
Når hændelseshandleren er færdig med at behandle, finder vi alle de beskidte vars og opretter en tilstandsopdatering til at sende til frontend.
Vi gemmer den nye tilstand i vores state manager, og sender derefter tilstandsopdateringen til frontend. Frontenden opdaterer derefter brugergrænsefladen for at afspejle den nye tilstand.
Jeg håber dette giver et godt overblik over hvordan Reflex virker under hætten. Vi vil have flere indlæg, der kommer ud for at dele, hvordan vi gjorde Reflex skalerbar og effektiv gennem funktioner såsom state sharding og compiler-optimeringer.