Isang federated app na iginuhit bilang mga teritoryo ng magkakaibang team, pinagdurugtong ng maninipis na shared rail at isang maliit na sentral na shell

Kanino ang alin: mga boundary, team at coupling sa isang federated React Native app

Ang isang ordinaryong React Native app ay hindi kailanman nagkakaroon ng ganitong debate. Isang team, isang repo, isang store. Ang screen ay isang folder, ang shared component ay isang import, at ang tanong kung kanino ang isang depinisyon ng data ay may sagot na sobrang obvious kaya walang nagsasabi nito nang malakas: sa team, dahil iisa lang ang team.

Sadyang binaklas ng mga post 4 hanggang 6 ng series na ito ang kaayusang iyon: dalawang tab feature na nagbi-build at naglalabas nang mag-isa, isang screen na naglalakbay sa pagitan nila bilang versioned na package, isang store na pinupuno sa runtime ng code na hindi pa nakita ng shell kailanman. Bawat hakbang ay pumilit ng isang desisyon sa pagmamay-ari: saan nakatira ang shared screen, sino ang puwedeng humawak sa store, saan nakaupo ang isang depinisyon ng data. Bawat desisyon ay nakatanggap ng isang talata sa sandaling ginawa ito ng tutorial. Tinitipon ng post na ito ang mga desisyong iyon at ipinagtatanggol nang minsanan, bilang isang buo.

Nakasulat ito bilang isang set ng mga test, hindi mga hatol. Bawat seksyon ay naglalahad ng pangangatwiran at ng mga kondisyong pinaninindigan nito; baguhin ang mga kondisyon (bilang ng team, tiwala sa pagitan nila, bilis ng release, laki ng platform team) at ilang sagot ay tapat na magbabago. Dalhin mo ang mga test, itapat sa sarili mong org chart, at kontrahin ang series na ito nang may katumpakan.

Ang unang test: kailangan mo ba ang alinman dito?

Bawat problema sa sanaysay na ito ay binili, hindi minana. Ang isang single-team app na naka-organisa nang feature-first ay gumuguhit na ng mga boundary na mahalaga: isang folder bawat domain, kasama ang mga screen, hook at API call ng domain sa loob, at bawat pagtawid ay nakikita sa review bilang isang import. Walang gastos sa pagpapatakbo ang modelong iyon: walang registry, walang version number, walang pangalawang dev server. Ang isang boundary ay nare-redraw sa isang hapon sa paglilipat ng file, at ang isang emergency ay nakakatawid dito gamit ang isang import at isang TODO.

Ang federation ang nangyayari kapag ang mga folder ay naging mga team. Ang kahinaan ng folder model ay ang release train, hindi ang mga boundary: bawat folder ay lumalabas pa rin sa iisang binary, sa iisang petsa, hinihila ng pinakamabagal na feature. Kapag ang magkakahiwalay na team ay nangailangan ng magkakahiwalay na petsa ng release, ang folder boundary ay kailangang maging deployment boundary, at lahat ng tanong sa sanaysay na ito ay nagmumula sa pag-angat na iyon.

Kaya ang unang test ang pinakamura, at hinaharangan nito ang iba.

Ang gate. Mahigit ba sa isang team ang naglalabas ng app na ito? Kung hindi, tumigil ka sa mga folder. Lahat ng test sa ibaba ay nag-aassume na oo ang sagot.

Tinitimbang nang buo ng unang post ng series na ito ang palitan at lumalapag kung saan lumalapag ang talatang ito: ginagawang teknikal na makinarya ng federation ang organisasyonal na friction, at ang app na walang friction ay nagbabayad para sa makinaryang hindi nito ginagamit kailanman.

Ang boundary ay isang team, hindi isang screen

Nang mangailangan ang Pokémon detail screen ng tahanang maaabot ng dalawang tab, kusang lumitaw ang federated na sagot: isang pangatlong remote, sa sarili nitong port, dineklara sa mga mapa ng dalawang consumer. Binigyan ito ng post 5 ng patas na pagdinig: gumagana ito nang mekanikal, nireresolba ng Module Federation ang mga nested remote nang walang reklamo, at nananatiling updatable ang screen nang hindi ginagalaw ang alinmang consumer. Hindi pa rin ang sagot, sa pangungusap na pinagmulan ng buong sanaysay na ito: ang federation boundary ay isang deployable, at hinahati ng mga team ang mga deployable ayon sa domain.

Ang pagtanggi ay tungkol sa gastos ng pagpapatakbo. May dala ang isang remote na mga gastos na hindi kilala ng folder: isang dev server na dapat patakbuhin, isang pipeline na dapat panatilihing berde, isang bersyong dapat i-publish, at sariling failure mode kapag hindi dumating ang bundle nito sa network. Ang mga gastos na iyon ay fixed bawat boundary, at ini-absorb sila ng isang team bilang overhead ng domain na aariin naman nila. Ang isang screen ay walang team sa likod. Pakawalan ang isang screen bilang sarili nitong deployable at darating ang overhead habang hindi darating kailanman ang may-ari; ibigay ang katayuang iyon nang minsan at wala nang prinsipyadong lugar para tanggihan ang susunod na screen. Ang dulo ng daang iyon ay isang app bawat screen, at isang release process na puro koordinasyon at walang paglalabas.

hinati ayon sa domain: isang team bawat kahonPokédex domainlahat ng screen nitoparty domainlahat ng screen nitohinati ayon sa screen: walang may-arilistdetailsettings

Kaya ang screen ay inilabas bilang @pokedex/detail: isang package sa registry, ini-install ng dalawang tab, mino-mount ng bawat isa sa loob ng sarili nitong stack. Ang mga tab pa rin ang mga deployable; ang screen ay isang dependency. Ito ang batas ni Conway na sinasadyang gamitin: kung isasalamin din naman ng sistema ang istruktura ng komunikasyon, iguhit ang mga linya ng module kung saan naroon na ang mga linya ng team.

Test 1. May team ba sa likod ng boundary na ito? Pumuputol ang test sa dalawang direksyon: tinatanggihan nito ng remote ang mga screen, at binibigyan nito ng remote ang mga domain na kinipot na ng kanilang folder.

Ang shared component ay nagre-render ng ibinibigay dito

May isang kagalang-galang na pattern ang mga web micro-frontend kung saan dumarating na matalino ang isang shared fragment: kinukuha nito ang sarili nitong data, hawak ang sarili nitong mga loading state, at bumabagsak sa kahit anong host bilang tapos na feature. Totoo ang appeal. Isang team ang naghahawak ng pixels at data sa likod ng iisang seam, isang linya lang ang isinusulat ng mga consumer, at ang isang data fix ay lumalabas nang walang consumer na nagbabago ng code. Maraming web micro-frontend setup ang gumagana nang ganito nang maayos, sa bilis ng web deployment.

Ang presyo ay nasa kung ano ang kailangang dalhin ng fragment. Ang component na nagfe-fetch ay may dalang opinyon tungkol sa bawat layer sa ilalim nito: isang HTTP client, isang cache, isang retry policy, isang kuwento ng auth. Minamana ng bawat consumer ang mga opinyong iyon nang hindi nakikita. Dalawang ganitong fragment sa iisang screen ay puwedeng magkasalungat sa lahat ng iyon. Sa mobile mas mabigat ang mana, dahil kung ano man ang dala ng fragment ay nadodoble sa bundle na kailangang i-download ng telepono.

Iginuhit ng post 6 ang linya sa kabilang direksyon. Tumatanggap ang PokemonDetailView ng isang Pokémon, tatlong state flag at isang retry callback, at nire-render ang mga ito. Iyon ang buong surface, at ang buong kaayusan ay dalawang linya sa container ng consumer:

const { data, isLoading, isError, refetch } = useGetPokemonDetailQuery(route.params.id);
return <PokemonDetailView pokemon={data} loading={isLoading} error={isError} onRetry={refetch} />;

Walang naidagdag na kahit isang dependency sa package ang pagiging live: ang parehong apat na peer ng static na bersyon (ang contract, React, React Native, ang safe-area context) at wala ang Redux kahit saan, dahil kung saan nanggagaling ang data ay trabaho ng app at kung ano ang itsura nito ay trabaho ng library.

Test 2. Alam ba ng component na ito kung saan nanggagaling ang data nito? Ang oo ay nangangahulugang mali ang pagkakaguhit ng boundary: ang ibinabahagi ay isang piraso ng app ng isang team na nakasuot ng pangalan ng component.

Isang minuto lang ang praktikal na pagsusuri: i-render ito sa isang hubad na app gamit ang mga hardcoded na prop. Kung kailangan nitong umiral muna ang isang provider, isang client o isang store, hindi pa ito component.

Ang mga depinisyon ng data ay nakatira kasama ng kanilang domain

Hinati ng huling release na iyon ang screen sa dalawa, ang view sa isang package at ang fetch sa isang app, at kinailangan ng hati na ilagay ang depinisyon ng endpoint sa kung saan. Ang kung saang iyon ang panuntunan. Lumapag ang getPokemonDetail sa list app, katabi ng getPokemonList, dahil ang Pokémon data ay pag-aari ng Pokédex domain at ang list app ang tirahan ng domain na iyon. Ang view ay pinapakain ng resulta bilang props at hindi nalalaman kailanman na umiiral ang endpoint.

Sabi ng kabaligtarang instinct, ang data code ay infrastructure at kasama ng iba pang infrastructure, sa baba sa shared layer. Hinahati ng series ang claim na iyon sa dalawa. Ang baseApi, ang HTTP layer kasama ang cache at tag machinery nito, ay talagang infrastructure: wala itong dalang kaalaman sa domain, at umaasa na rito ang bawat team gaya ng pag-asa nila sa React, kaya nakatira ito sa contract package at lahat ay kumakabit dito. Iba ang isang feature endpoint. Nakapaloob dito kung ano ang kinukuha ng isang domain, paano nito pina-parse, at ano ang inilalantad nito, at kung sino man ang may-ari ng depinisyong iyon ay nagiging may-ari ng mga tanong, mga bug at mga migration na kasama nito. Ilipat ang mga endpoint ng isang domain sa shared layer at ang mga may-ari ng layer ay dahan-dahang nagiging may-ari ng data plumbing ng bawat domain, nang wala ang kaalaman sa domain na magpapaposible sa trabaho.

Test 3. Nakatira ba ang depinisyong ito kasama ng domain nito? Ang makinaryang walang kaalaman sa domain ay ibinabahagi; ang isang endpoint, isang parser, isang schema para sa data ng isang domain ay naglalakbay kasama ng domain.

Mas mura ang pagdodoble kaysa sa dependency sa pagitan ng mga team

Sa susunod na post, magsisimulang maghawak ang party remote ng sarili nitong state, at ang una nitong pangangailangan ay Pokémon data para sa isang tinapik na slot: data na alam nang kunin ng Pokédex domain. Ang maayos na instinct ay nagmumungkahi ng shared data package: isang depinisyon, dalawang consumer, walang drift. Gagawin ng series ang magulong bagay: susulat ang party team ng sarili nitong endpoint file. Aabot ito sa mga dalawampung linya at mukhang kabiguan sa paglilinis. Ang file na dinodoble nito, ang sa mismong Pokédex, pinutol sa hugis nito:

const detailApi = baseApi.injectEndpoints({
  endpoints: build => ({
    getPokemonDetail: build.query<PokemonDetail, number>({
      async queryFn(id, _api, _extra, baseQuery) {
        const res = await baseQuery(`pokemon/${id}`);
        return res.error ? { error: res.error } : { data: parsePokemonDetail(res.data) };
      },
    }),
  }),
});

Bilangin kung magkano ang maayos na bersyon. Ang isang data package na pinaghahatian ng dalawang feature team ay isang dependency sa pagitan ng magkapantay. Bawat pagbabago rito ay bumabagsak sa dalawang team, kaya bawat pagbabago ay nangangailangan ng pagsang-ayon ng dalawa. Nagkakadena ang mga release: i-bump ang package, hintayin ang kabilang team na kunin ang bump, saka ilabas ang talagang gusto mong ilabas. At ang package mismo ay nangangailangan ng may-ari: isa sa dalawang team ang sasagot sa mga bug nito, o walang sasagot. Ang isang shared na depinisyon sa pagitan ng magkapantay ay hindi nag-aalis ng koordinasyon; ginagawa nitong iskedyul na dapat mong tuparin ang drift na nakikita mo. May litrato na ang series kung ano ang itsura ng isang versioned na dependency sa pagitan ng mga team kapag naghiwalay ang mga kalendaryo. Hinayaan ng maluwag na peer range ng post 5 na ipares ng app ng isang team ang lumang contract sa bagong screen, nanatiling berde ang bawat compiler, at nakatanggap ang isang user ng pangungusap na may butas:

Ang Pokémon detail screen na nagbabasa ng 'Opened from the' at wala nang kasunod, dahil may hawak ang list app na mas lumang bersyon ng contract at hindi kailanman ipinasa ang field na nire-render ng mas bagong screen

Ang dalawampung nadobleng linya ay nagkakahalaga ng dalawampung linya. Sumusunod ang bawat kopya sa pangangailangan ng sarili nitong team at lumalabas sa mga petsa ng sarili nitong team, at ang drift sa pagitan nila ay may matigas na hangganan, dahil parehong pina-parse ng dalawa ang parehong wire format mula sa parehong backend. Kapag lumaki ang pagdodoble lampas sa isang file (limang endpoint, sampu), nagbabago ang sagot, at sinasabi ng susunod na seksyon kung paano malalaman.

Ang coupling test

Kailangan ng panuntunan ng pagdodoble ng hangganan, dahil ang mga team ay kumakabit sa shared code sa lahat ng oras at tama sila: React, ang navigation runtime, baseApi, ang mga type ng contract. Ang linya sa pagitan ng mga iyon at ng peer data package ang pinaka-magagamit-mulit na test sa sanaysay na ito.

Test 4. Umiiral na ba ang dependency na ito, o iniimbento ito ng pagbabahagi? Kumabit sa inaasahan mo na; tanggihan ang dependency na nililikha mismo ng pagbabahagi.

Ang isang client na ginenerate mula sa API specification ng backend ay ligtas ibahagi, dahil ikinakabit nito ang mga consumer nito sa backend, isang dependency na mayroon na silang lahat. Kapag nagbago ang specification, apektado na ang bawat consumer sa sandaling nagbago ito; inilalabas lang ng generated na client ang katotohanan sa build time sa halip na sa production. Pumapasa ang contract package sa parehong test: ang mga param at hugis ng module ay mga kasunduang nakatali na sa mga app nang implicit, isinulat kung saan nakikita ng bawat compiler. Bumabagsak dito ang peer data package. Walang anuman sa party domain ang umasa sa iskedyul ng release ng Pokédex team bago umiral ang shared package; ang package mismo ang lilikha ng dependency na iyon.

ang pagdodoble ay kumakabit sa umiiral naang backendPokédex: ang endpoint filenitoparty: ang dalawampung linyanitoang shared package ay nag-iimbento ng dependencybawat pagbabagobawat releasePokédex teamshared data packageparty team

Ang hila ng shell

Bawat panuntunan hanggang dito ay nagtutulak ng code palabas, patungo sa mga domain at package. May isang argumentong nagtutulak sa kabilang direksyon, at karapat-dapat ito sa pinakamahabang pagdinig sa sanaysay, dahil kadalasang ginagawa ito ng pinakamaingat na inhinyero sa silid.

Dumarami ang mga endpoint. Ilang team ang kumukuha ng nagsasapawang data. May nagmumungkahi ng obvious na konsolidasyon: iakyat ang shared na kakayahan sa shell, kung saan kayang hawakan ng platform team ang iisang depinisyon ng lahat. Isang HTTP layer, isang kuwento ng auth, isang set ng mga depinisyon ng data, consistent na behaviour dahil sa konstruksyon, imposibleng drift. Walang nagmumungkahi nito nang may masamang hangarin. Sa purong teknikal, naibibigay nito nang eksakto ang ipinapangako, at ang iisang depinisyon ay talagang mas madaling pag-isipan kaysa apat na kopya.

Dumarating ang mga gastos mula sa dalawang direksyon. Ang una ay organisasyonal, at bumabagsak bago pa maisulat ang anumang code: ang pag-escalate ng kakayahan sa shell ay ginagawang precondition ang platform team. Ang feature team na nangangailangan ng pagbabago sa antas ng shell ay hindi makakapagsimula hangga’t hindi ini-schedule, binubuo at inilalabas ng ibang team ang trabaho. Ang petsa ng simula nito ay nakaupo na ngayon sa loob ng sprint ng iba. I-multiply iyan sa bawat team na may kahilingan, at ang platform team ay nagiging pila sa harap ng buong programa, habang nagmamana, kahilingan bawat kahilingan, ng kaalaman sa domain na hindi nito inari kailanman at hindi nito kayang panatilihing napapanahon. Ang salitang dapat pagnilayan ay magsimula: ang feature team ay nakatigil, at nananatiling nakatigil hangga’t hindi naglalabas ang isang team na may ibang mga prayoridad.

Test 5. Makakapagsimula ba ang feature team nang walang ibang team na maglalabas muna? Ang pag-escalate ng kakayahan sa shell ay bumabagsak dito ayon sa disenyo.

Ang pangalawang gastos ay partikular sa mobile: ang shell ang binary, kaya ang shell code ay lumalabas sa bilis ng app store. Build, submit, review, staged rollout, tapos ang mahabang buntot ng mga user na hindi nag-a-update kailanman.

Ang kakayahang inilipat sa shell ay sumasakay sa tren ng app store, at ang isang maliit na pagbabago sa schema na sumasakay sa store review ang mismong gastos na tinakasan ng series na ito nang i-adopt ang federation. Tahimik itong ibinabalik ng escalation, isa-isang kakayahan.

May isang uri ng code na talagang napapasakamay ng platform layer, sumakay man ito sa binary ng shell o sa isang package na pag-aari ng platform gaya ng contract: ang mabagal na uri. Ang HTTP client, ang auth, ang makinarya ng baseApi, ang observability, ang mga runtime na nireresolba ng bawat remote. Ang pattern sa listahang iyon: kakayahang mabagal magbago, walang dalang kaalaman sa domain, at inaasahan na ng lahat. Bihirang lumaki ang listahan, at bawat dagdag ay nangangailangan ng argumento, dahil lahat ng nasa listahan ay nagmamana ng bilis ng binary.

Inner source: ang gumaganang gitna

Sa pagitan ng “lahat ng team ay naghihintay sa platform team” at “walang shared na kakayahan” ay may gitnang may pangalan sa malalaking organisasyon: inner source. Patakbuhin ang shared code na parang open-source project na nagkataong internal: ang shell, ang contract package, ang isang component library, ang isang shared data layer kung magpasya ang organisasyon mong magkaroon. Ang platform team ang mga maintainer at code owner. Ang mga product team ay nagpapadala ng mga pull request.

Natutunaw ang humaharang na precondition. Ang feature team na nangangailangan ng pagbabago sa antas ng shell ay mismong nagsusulat ng pagbabago, laban sa mga panuntunan ng kontribusyon ng shared repo, at naghihintay ng review sa halip na ng puwesto sa roadmap. Ang trabaho ng platform team ay lumilipat mula sa gawin-lahat patungo sa suriin-at-alagaan: hawak nito ang pamantayan ng kalidad at pagkakaugnay at tumitigil itong maging pila. Ang Pokédex team na nagdadagdag ng component sa shared library ay nagiging isang pull request, hindi isang ticket sa backlog ng ibang team. Gayundin ang dagdag na schema ng party team, kung shared data layer ang napili ng organisasyon mo.

Totoo ang mga gastos. Hindi nawawala ang latency ng review; ang isang pinagtatalunang pull request ay puwedeng maghintay nang kasintagal ng puwesto sa roadmap noon. Ang platform team ay napupunta sa pagmamantini ng code na hindi nito isinulat, isang tunay na pasanin sa araw na lumipat ang nag-ambag na team. At gumagana lang ang modelo sa ibabaw ng tunay na imprastruktura ng kontribusyon:

Bago ang unang PRKung saan tumatakbo ang inner source
Mga kombensyon ng kontribusyon na kayang sundan ng estranghero, nakasulat
CI na kayang patakbuhin ng nag-aambag na team nang hindi nagpapaalam kaninuman
Mga ownership file na nagruruta ng bawat review sa tamang mga tao

Nagbabayad ang inner source para sa sarili kapag sapat na kadalas dumating ang mga kontribusyon para bigyang-katwiran ang overhead na iyon.

Ang "mag-open ka na lang ng PR" laban sa isang repo na walang dokumentasyon ay isang kagandahang-asal, hindi isang proseso. Kung wala ang imprastruktura ng kontribusyon, ang inner source ay ang pila na may mas palakaibigang pangalan.

Ang mga test, at kung ano ang nagpapabago sa mga sagot

Kasya sa isang mapa ang buong argumento, bawat kahon ay hawak ang pag-aari nito at ang bilis ng paglabas nito:

party domain: ibang teamPokédex domain: isang teampag-aari ng platformnagmo-mount sa runtimenagmo-mount sa runtimenag-i-inject ng endpoints sanaka-publish sanag-i-install ng viewang shellwiring ng store · tab bar ·shared runtimeslumalabas bilang binary ngapp@pokedex/contracts: angmga kasunduantypes · baseApi · tag namessemver; naghihintay ngpahintulot ang mga majorlist remotemga screen + ang dalawangendpoint nitolumalabas kahit anong hapon@pokedex/detailisang view: props papasok,pixels palabassemverparty remoteang grid; state sa susunod napostlumalabas kahit anong haponregistrydinadaanan ng mga package

Limang test, sa pagkakasunud-sunod ng pagkakatagpo ng series sa kanila:

Ang limang testKanino ang alin
1May team ba sa likod ng boundary na ito? Ang mga domain ay tumatanggap ng remote; ang mga screen ay tumatanggap ng package.
2Alam ba ng component na ito kung saan nanggagaling ang data nito? Ang shared component ay nagre-render ng ibinibigay dito; dumarating ang data bilang props.
3Nakatira ba ang depinisyong ito kasama ng domain nito? Ang makinarya ay ibinabahagi; ang mga endpoint, parser at schema ay naglalakbay kasama ng domain.
4Umiiral na ba ang dependency na ito, o iniimbento ito ng pagbabahagi? Kumabit sa backend na inaasahan mo na; tanggihan ang peer package na gumagawa ng dependency sa iskedyul sa pagitan ng mga team.
5Makakapagsimula ba ang feature team nang walang ibang team na maglalabas muna? Kung hindi, nasa maling antas ang kakayahan, o ang prosesong nakapalibot dito.
Lahat ng lima ay nasa likod ng gate: mahigit sa isang team ang naglalabas ng app na ito. Kung hindi, sinasagot silang lahat ng mga folder nang libre.

Walang isa man dito ang nagbibigay ng iisang sagot para sa bawat organisasyon; sinasabi nila kung magkano ang bawat opsyon sa iyo. Dalawang team na may tiwala sa isa’t isa at sabay naglalabas ay puwedeng maghatian ng data package at halos hindi maramdaman ang coupling. Ang platform team na iisang tao ay hindi kayang mag-review sa bilis ng pag-ambag ng limang team, at ang inner source ay bumabalik sa pilang nilayon nitong palitan. Ang app na may tatlong team at quarterly na bilis ay puwedeng panatilihin ang lahat sa shell at hindi mapansin kailanman ang presyo. Kung iba ang pagkakaputol ng organisasyon mo sa mga linyang ito at kumakapit ang mga tahi, iyon ay ibang sagot sa parehong mga test, at karapat-dapat ipagtanggol sa sarili nitong mga termino.

Diretsong pinagtatrabaho ng susunod na post ang mga test. Walang laman ang party grid mula pa post 4, dahil walang anuman sa app ang nakakaabot dito. Malapit na itong punuin ng party team ng state na pag-aari nito, at ang mga unang desisyon sa daan ay eksaktong ang mga ipinagtanggol lang ng sanaysay na ito: kanino ang slice, saan pupunta ang endpoint file, ano ang dala ng contract. Ang mga panuntunan ay titigil na maging prosa at magsisimulang maging mga file.

Mga Sanggunian

  • Micro Frontends — ang pagsusuri ni Cam Jackson, kasama ang self-contained fragment pattern na kinokontra ng sanaysay na ito para sa mobile
  • Batas ni Conway — ang papel ng 1968: isinasalamin ng mga sistema ang mga istruktura ng komunikasyon ng mga organisasyong bumubuo sa kanila
  • InnerSource Commons — ang praktis, ang mga pattern nito, at ang imprastruktura ng kontribusyon na inaasahan nito
  • Team Topologies — sina Skelton at Pais tungkol sa mga platform team at stream-aligned team, ang bokabularyo sa likod ng debate ng shell
  • react-native-module-federation — ang kasamang repo na ang mga desisyon ay ipinagtatanggol ng sanaysay na ito
Warren de Leon
Warren de Leon

Software Engineering Manager. Pinakahuling pinamunuan ang Mobile Platform team sa Hargreaves Lansdown. Sumusulat tungkol sa engineering leadership, React Native, at pagbuo ng magagandang team.

Tingnan ang profile