macOS · Windows · Zbudowane w całości przez Metisorę

Jak zbudowaliśmy MetiRecon i wydaliśmy go w Mac App Store oraz Microsoft Store

MetiRecon jest nasz od pierwszej do ostatniej linijki: desktopowa aplikacja działająca offline, z silnikiem rekoncyliacji napisanym w Go, osadzonym wewnątrz interfejsu we Flutterze, wydana w Mac App Store i w Microsoft Store. Sami wybraliśmy ten problem i doprowadziliśmy go aż do wydania w dwóch sklepach. Ten case study to inżynieria, która za tym stoi - decyzje architektoniczne, trudne momenty publikacji natywnej aplikacji desktopowej w dwóch sklepach naraz, i sposób, w jaki doprowadzamy produkt od pustego repozytorium do czegoś, co ludzie mogą zainstalować i za co płacą. Projektując dla klientów, trzymamy się dokładnie tych samych zasad.

Platformy
macOS, Windows
Stack
Flutter, Go, natywne FFI
Dystrybucja
Mac App Store, Microsoft Store
Dane
100% offline
Ekran aplikacji MetiRecon z wynikami rekoncyliacji - wiersze dopasowane i niedopasowane
MetiRecon po przebiegu: wiersze dopasowane, wiersze niedopasowane i sklasyfikowane różnice w jednym widoku.
Problem
Zespoły finansowe i audytowe robią rekoncyliację w arkuszach, które zawodzą, gdy tylko pliki rosną, reguły się komplikują albo audytor pyta, jak powstało dane dopasowanie.
Co zbudowaliśmy
Aplikację desktopową działającą w pełni offline - silnik dopasowań w Go wewnątrz interfejsu Flutter - która klasyfikuje każdą różnicę i tworzy raport gotowy pod audyt.
Efekt
Wydana w Mac App Store i Microsoft Store, natywna na Apple Silicon i Intel, dopasowująca miliony wierszy w sekundy - a dane nigdy nie opuszczają komputera.

Co znajdziesz w tym case study

  1. Produkt, który postanowiliśmy zbudować
  2. Jak podeszliśmy do budowy
  3. Dlaczego zbudowaliśmy go w pełni offline
  4. Domena, którą musieliśmy zamodelować
  5. Modelowanie typów różnic
  6. Projektowanie silnika dopasowań w Go
  7. Osadzenie Go wewnątrz Fluttera
  8. Odczyt siedmiu formatów plików
  9. Projektowanie interfejsu desktopowego
  10. Raporty gotowe pod audyt
  11. Publikacja w Mac App Store
  12. Publikacja w Microsoft Store
  13. Jedna binarka na Apple Silicon i Intela
  14. Subskrypcje rozliczane przez sklepy
  15. Testowanie narzędzia, które musi być poprawne
  16. Co ten projekt mówi o tym, jak pracujemy
  17. Co powiedzielibyśmy innemu zespołowi
  18. Najczęstsze pytania klientów

Produkt, który postanowiliśmy zbudować

MetiRecon zaczął się od naszego własnego pomysłu, nie od briefu klienta, co oznaczało, że sami podejmowaliśmy każdą decyzję: co budujemy, dla kogo i jak daleko to doprowadzamy. Wybraliśmy problem, który dobrze rozumieliśmy. Zespoły finansowe i audytu nieustannie rekoncyliują dane, prawie zawsze w arkuszu kalkulacyjnym, a arkusz zawodzi je w momencie, gdy pliki robią się duże, reguły bardziej zawiłe, albo audytor pyta, na jakiej podstawie dane dopasowanie zostało zrobione. Wybór na rynku jest dziś zerojedynkowy: arkusz, który wysypuje się przy większych plikach, albo platforma wdrażana miesiącami przez konsultantów. Dla kogoś, kto ma dwa eksporty i potrzebuje odpowiedzi dziś po południu, nie ma nic pomiędzy.

Zadanie postawiliśmy sobie konkretne. Zbudować desktopowe narzędzie, które porównuje dwa zbiory danych, klasyfikuje każdą różnicę i generuje raport, który audytor może zatwierdzić. Zrobić je na tyle szybkie, żeby radziło sobie z plikami, których arkusz odmówiłby otwarcia. Zrobić je używalne dla kogoś, kto nigdy nie napisał linijki kodu. Trzymać dane na maszynie użytkownika. I wydać je przez sklepy, żeby instalowało się jak każda inna profesjonalna aplikacja. Dalej opisujemy, jak z tego briefu powstał produkt obecny w dwóch sklepach i jakie decyzje inżynierskie nas tam doprowadziły.

Prawdziwe pytanie nie brzmi „czym te dwa pliki się różnią", tylko „które instrumenty się rozjechały i dlaczego" - na milionach wierszy, w kilka sekund. Unixowy diff tego nie robi, a skomplikowanego SELECT-a trzeba najpierw napisać.

Jak podeszliśmy do budowy

Kolejność prac jest tu decyzją projektową i podjęliśmy ją wbrew tej oczywistej: interfejs powstał na końcu. Zaczęliśmy od dwóch rzeczy, które najłatwiej mogły pogrążyć projekt: od silnika dopasowań, bo gdyby nie był jednocześnie poprawny i szybki na lokalnej maszynie, produkt nie miałby racji bytu, i od ścieżki wydania, bo publikacja natywnej aplikacji desktopowej z osadzoną natywną biblioteką w dwóch różnych sklepach to dokładnie ten etap, na którym pierwsze wydania grzęzną.

Branie najbardziej ryzykownych części na pierwszy ogień to świadomy nawyk. Dzięki temu, zanim doszlifowywaliśmy interfejs, pytania, które mogłyby wymusić przepisanie od nowa - czy silnik w Go da się czysto umieścić wewnątrz aplikacji Flutter, czy build w sandboxie i po notaryzacji faktycznie przejdzie review, czy ten sam kod spakuje się dla Microsoft Store - były już odpowiedziane. Dużo taniej jest odkryć twarde ograniczenie w drugim tygodniu niż w ósmym, a budowanie w tej kolejności to jeden z głównych powodów, dla których produkty, których się podejmujemy, trafiają do sklepu, zamiast utknąć tuż przed nim.

Reszta tego opisu podąża tą samą logiką, którą szła budowa: najpierw silnik i architektura, potem produkt wokół niego, a na końcu długa, mało efektowna robota przeprowadzenia go przez dwa review w sklepach. Ta kolejność nie jest specyficzna dla MetiReconu. Pracujemy w tej sekwencji zawsze, gdy trudną częścią nie jest koncepcja, tylko dostarczenie.

Dlaczego zbudowaliśmy go w pełni offline

Najważniejsza decyzja projektowa w MetiReconie to ta, która nigdy nie pojawia się na ekranie: dane nigdy nie opuszczają maszyny. Nie ma przesyłania, nie ma przetwarzania w chmurze, nie ma konta, które trzeba założyć przed pierwszym porównaniem, i nie ma potrzeby połączenia z internetem, żeby w ogóle uruchomić rekoncyliację. Wszystko dzieje się lokalnie, na komputerze osoby, która wykonuje pracę.

Ma to znaczenie ze względu na to, czym zwykle są dane do rekoncyliacji. Wyciągi bankowe, zapisy transakcji, dane płacowe, księgi wewnątrzgrupowe, historie transakcji klientów: to jedne z najbardziej wrażliwych informacji, jakie firma posiada. Bardzo wiele organizacji ma polityki, które wprost zakazują wysyłania takich plików do zewnętrznej usługi, i słusznie. W momencie, gdy dane opuszczają firmę, stają się czyimś problemem bezpieczeństwa, czyimś ryzykiem wycieku i pozycją w każdej ocenie ryzyka dostawcy oraz umowie powierzenia danych, którą firma musi podpisać.

Trzymając wszystko na urządzeniu, MetiRecon usuwa całą tę kategorię obiekcji. Nie ma umowy powierzenia do negocjowania, bo nigdy nie przetwarzamy tych danych. Nie ma regionu, o który trzeba się martwić, bo plik nigdzie nie podróżuje. Zespół bezpieczeństwa może zaakceptować narzędzie na podstawie jednego, łatwo weryfikowalnego faktu: pliki, które użytkownik wczytuje, nigdy nie opuszczają maszyny. Dla produktu sprzedawanego do finansów i audytu ta cecha jest warta więcej niż jakakolwiek funkcja. Zamienia trwającą miesiącami procedurę zakupową w zwykłe pobranie aplikacji.

Wybór trybu offline ukształtował też wszystko poniżej. Wykluczył łatwą drogę robienia ciężkich obliczeń na serwerze i uczynił lokalną maszynę odpowiedzialną za całą pracę - i właśnie dlatego silnik dopasowań musiał być szybki i oszczędny pamięciowo, a nie tylko poprawny. Sama prywatność nikogo nie zatrzyma. Jeśli porównanie dwóch plików trwa kwadrans, użytkownik po tygodniu wraca do arkusza.

Domena, którą musieliśmy zamodelować

Zanim napisaliśmy silnik, musieliśmy porządnie zamodelować problem, bo budowanie oprogramowania dla danej dziedziny oznacza rozumienie jej lepiej niż ludzie, którzy będą go używać. Osoby spoza finansów wyobrażają sobie rekoncyliację jako proste sprawdzenie równości: zestaw dwie listy i zobacz, gdzie się zgadzają. Prawdziwa rekoncyliacja jest znacznie bardziej złożona, a poprawne uchwycenie niuansów w modelu danych to większość pracy. Trzy z nich ukształtowały cały silnik.

Pierwszy niuans to klucz. Dwa rekordy reprezentują to samo zdarzenie, gdy zgadza się jakaś kombinacja ich pól, a ta kombinacja rzadko jest jednym schludnym identyfikatorem. Może to być numer referencyjny, albo data plus kwota plus kontrahent, albo konto plus data waluty z kwotą, której wolno różnić się w granicach zaokrąglenia. MetiRecon pozwala użytkownikowi zdefiniować, co znaczy „to samo", bo tylko on zna kształt swoich danych, a potem stosuje tę definicję spójnie w każdym wierszu.

Drugi niuans to różnica. Gdy dwa rekordy się nie zgadzają, ciekawe jest nie to, czy się różnią, ale jak. Kwota rozjeżdżająca się o kilka groszy to różnica z zaokrąglenia. Zapis, który pojawia się w jednym pliku dziś, a w drugim jutro, to różnica czasowa, nie błąd. Transakcja, która istnieje po jednej stronie i nigdzie po drugiej, to prawdziwa pozycja niedopasowana, którą ktoś musi zbadać. To właśnie wrzucanie tego wszystkiego do jednego worka „różnice" spowalnia ręczną rekoncyliację, bo człowiek musi potem posortować to z powrotem ręcznie. MetiRecon klasyfikuje każdą różnicę, tak żeby wyjątki, które naprawdę wymagają uwagi, wypływały na wierzch.

Trzeci niuans to liczność. Nie każde dopasowanie jest jeden do jednego: pojedyncza faktura bywa rozliczona kilkoma płatnościami, a pojedyncza płatność pokrywa kilka faktur. W pierwszym wydaniu świadomie oparliśmy silnik na kluczu definiowanym przez użytkownika - to on decyduje, co znaczy „ten sam rekord", i to on zawęża klucz tam, gdzie liczność wchodzi w grę. Pełna obsługa relacji jeden do wielu i wielu do wielu to następny krok w rozwoju produktu.

Modelowanie typów różnic

Klasyfikacja różnic była jedną z kluczowych decyzji modelowych w silniku i celowo utrzymaliśmy tę taksonomię małą. Klasyfikacja, której użytkownik nie potrafi przewidzieć, jest gorsza niż żadna, bo przestaje ufać etykiecie i wraca do czytania wierszy ręcznie. Każdy rekord, który nie dopasował się czysto, trafia do jednego z trzech koszyków.

Rekordy mismatched są tymi interesującymi: klucz dopasował się po obu stronach, więc mowa o tym samym zdarzeniu, ale jedno lub więcej porównywanych pól się nie zgadza. Oba systemy wiedzą o tej transakcji i opowiadają o niej co innego.

Pre-orphans i post-orphans to rekordy obecne po jednej stronie i nieobecne po drugiej. Rozdzielenie sierot według strony, z której pochodzą, znaczy więcej, niż się na pierwszy rzut oka wydaje. „Brakuje w księdze" i „brakuje w wyciągu" to dwa różne dochodzenia, często należące do dwóch różnych osób, a zsypanie obu na jedną kupkę „niedopasowanych" oddaje analitykowi robotę ręcznego przesortowania ich z powrotem - czyli dokładnie to, co narzędzie miało zdjąć z głowy.

To, do którego koszyka trafi rekord, zależy od konfiguracji porównania, a ta należy do użytkownika, nie do nas. Pola liczbowe można porównywać z tolerancją wyrażoną procentowo albo kwotowo. Dwa systemy zaokrąglające w innym miejscu lub przeliczające po nieco innym kursie generują różnice, które są szumem, a nie błędem - a narzędzie raportujące każdą z nich zasypuje prawdziwe problemy stertą drobiazgów.

Pola tekstowe mają własne przełączniki: czułość na wielkość liter włączona lub nie oraz usuwanie białych znaków włączone lub nie. Prawdziwe eksporty rozjeżdżają się na obu tych rzeczach bez przerwy, bo jeden system dopełnia referencję spacjami, a drugi zamienia ją na wielkie litery - i żadna z tych różnic nie oznacza, że rekordy pod spodem są inne. Jawne przełączniki zamiast cichej domyślnej magii utrzymują powtarzalność przebiegu: te same dwa pliki przy tych samych ustawieniach klasyfikują się identycznie za każdym razem, a właśnie tego wymaga ślad audytowy.

Projektowanie silnika dopasowań w Go

Sercem MetiReconu jest silnik dopasowań, a napisaliśmy go w Go. Ten wybór był świadomy i to on wyznaczył dużą część architektury, która potem powstała.

Go jest szybki, kompiluje się do pojedynczej natywnej binarki bez środowiska uruchomieniowego do instalowania i pozwala z jednego kodu zbudować wersje pod różne architektury, co ma ogromne znaczenie, gdy ten sam silnik musi działać na Apple Silicon, Intelowych Macach i na Windowsie. Równie ważne, Go dobrze radzi sobie z rodzajem pracy, jakiej wymaga rekoncyliacja: strumieniowaniem przez duże pliki, trzymaniem ustrukturyzowanych rekordów w pamięci w oszczędny sposób i wykonywaniem porównań równolegle na wszystkich rdzeniach, jakie ma nowoczesny laptop. Tam, gdzie arkusz przelicza cały świat przy każdej zmianie, silnik w Go czyta każdy plik raz, buduje potrzebne indeksy i wykonuje dopasowanie w jednym przemyślanym przejściu.

Dopasowanie dokładne, tolerancyjne i rozmyte

Silnik obsługuje kilka trybów dopasowania, bo prawdziwe dane potrzebują ich wszystkich. Dopasowanie dokładne to baza: pola kluczowe zgadzają się co do bajta. Dopasowanie tolerancyjne pozwala polom liczbowym różnić się w skonfigurowanym zakresie - tak pochłaniane są zaokrąglenia i drobne różnice kursowe, bez oznaczania ich jako błędów. Dopasowanie rozmyte obsługuje bardziej pokręcone przypadki, gdzie referencja została po każdej stronie zapisana nieco inaczej, albo nazwa jest w jednym systemie skrócona, a w drugim rozpisana. Każdy tryb to narzędzie, a to użytkownik decyduje, który stosuje się do którego pola.

Klucze, indeksy i wydajność

Wydajność na lokalnej maszynie sprowadza się do nierobienia zbędnej pracy. Silnik buduje indeksy haszujące na polach kluczowych, tak że znajdowanie kandydatów na dopasowanie jest bliskie stałego czasu, a nie zagnieżdżonym skanowaniem każdego wiersza względem każdego innego. Klucze złożone są haszowane razem, tolerancje obsługiwane przez kubełkowanie wartości, tak żeby bliskie dopasowania trafiały do sąsiednich kubełków, a całe porównanie jest zaprojektowane tak, by trzymać w pamięci naraz tylko to, co konieczne. Efekt to narzędzie, które rekoncyliuje pliki z milionami wierszy w sekundy na zwykłym sprzęcie, daleko poza punktem, w którym arkusz odmawia współpracy, i to bez żadnego serwera.

Pamięć potraktowaliśmy jako twarde ograniczenie, a nie coś, na co się liczy. Rozmiar rekoncyliacji nie jest zawężony ilością RAM-u w maszynie: silnik dzieli obie strony na fragmenty, rekoncyliuje fragment z fragmentem i trzyma w pamięci wyłącznie to, czego wymaga bieżące porównanie. Dzięki temu można porównać zbiór znacznie większy niż dostępna pamięć, a zużycie pozostaje płaskie, zamiast rosnąć razem z plikiem. Arkusz takiego pliku w ogóle nie otworzy, a naiwne narzędzie wczyta obie strony i padnie.

Jest jeszcze jedna cecha, którą silnik musiał mieć: determinizm. Te same dane wejściowe i te same reguły muszą dawać dokładnie ten sam wynik za każdym razem, niezależnie od tego, ile rdzeni wykonuje pracę i w jakiej kolejności. W obliczeniach równoległych nie dzieje się to samo z siebie - trzeba świadomie zaprojektować, jak scalane są cząstkowe wyniki, tak żeby zrównoleglenie przyspieszało pracę, ale nie wpływało na jej rezultat. Dla narzędzia, którego raporty trafiają do audytu, powtarzalny wynik jest równie ważny jak szybki.

Osadzenie Go wewnątrz Fluttera

MetiRecon ma silnik w Go i interfejs we Flutterze, a sprawienie, żeby te dwa światy czysto się dogadywały, było jedną z bardziej wymagających technicznie części budowy.

Oczywistą alternatywą byłoby przepisanie logiki dopasowań w Dart, języku, którego używa Flutter, i uniknięcie problemu interoperacyjności w ogóle. Nie zdecydowaliśmy się na to z dwóch powodów. Po pierwsze, silnik to część produktu, w której najbardziej liczą się poprawność i szybkość, a Go daje nam jedno i drugie znacznie mniejszym wysiłkiem niż zajęłoby przepisywanie indeksowania, haszowania i równoległego dopasowywania w Darcie. Po drugie, samodzielny silnik jest wielokrotnego użytku. Ten sam rdzeń, który napędza aplikację desktopową, dałoby się w zasadzie sterować z wiersza poleceń albo z zadania wsadowego, w ogóle nie dotykając interfejsu.

Zamiast przepisywać, skompilowaliśmy więc silnik w Go do natywnej biblioteki - biblioteki dynamicznej na każdej platformie - i wołaliśmy do niej z Fluttera przez interfejs funkcji obcych (FFI) w Darcie. Strona Fluttera odpowiada za wszystko, co widzi użytkownik: wczytywanie plików, konfigurowanie dopasowania, pokazywanie postępu i renderowanie wyników. Strona Go odpowiada za obliczenia. Dane przekraczają granicę przez starannie zaprojektowany interfejs, tak żeby dużych zbiorów nie kopiować tam i z powrotem bez potrzeby, a ciężka praca dzieje się poza wątkiem interfejsu, więc interfejs nigdy nie zamiera w trakcie rekoncyliacji.

Flutter jest właścicielem wszystkiego, co widzi użytkownik. Go jest właścicielem obliczeń. Granica między nimi jest mała, jawna i szybka.

Ta architektura ma przyjemną własność: każda strona gra do swoich mocnych stron, a szew między nimi jest mały. Flutter świetnie buduje spójny, natywnie wyglądający interfejs desktopowy z jednej bazy kodu na macOS i Windowsie. Go świetnie przemiela dane. Utrzymanie wąskiej granicy między nimi - garść dobrze zdefiniowanych wywołań zamiast gadatliwej wymiany tam i z powrotem - utrzymało całość w łatwości utrzymania i znacznie ułatwiło rozumowanie o wydajności.

Odczyt siedmiu formatów plików

Narzędzie do rekoncyliacji jest przydatne tylko wtedy, gdy potrafi odczytać pliki, które ludzie już mają, a dane finansowe przychodzą w zdumiewającej różnorodności kształtów. MetiRecon czyta CSV, TSV, Excel, JSON, XML, tekst o stałej szerokości (fixed-width) i komunikaty protokołu FIX, a każdy z nich przyniósł własną małą udrękę.

CSV brzmi prosto i nigdy taki nie jest. Separatory różnią się między przecinkiem, średnikiem i tabulatorem, zależnie od systemu i ustawień regionalnych, które utworzyły plik. Kodowania znaków się różnią, a plik wyeksportowany na jednej maszynie może nieść znacznik kolejności bajtów (BOM) albo używać starej strony kodowej, która zamienia znaki diakrytyczne w bełkot, jeśli założysz, że wszystko jest nowoczesnym Unicode. Reguły cytowania się różnią, znaki nowej linii wewnątrz cytowanych pól są w wyeksportowanych danych częste, a liczby są formatowane z różnymi separatorami tysięcy i dziesiętnych na całym świecie. Parser musi być wyrozumiały na to wszystko, nie odczytując przy tym po cichu wartości błędnie, bo źle odczytana liczba w rekoncyliacji jest gorsza niż awaria.

Excel oznacza czytanie prawdziwej binarnej struktury arkusza, w tym wielu arkuszy, typowanych komórek, dat zapisanych jako liczby seryjne i formuł, których zapamiętane wyniki trzeba odczytać, a nie przeliczać. JSON i XML wnoszą zagnieżdżenie: dane finansowe w tych formatach są często głęboko ustrukturyzowane, a narzędzie musi pozwolić użytkownikowi spłaszczyć do wierszy te części, na których mu zależy. Pliki o stałej szerokości, wciąż częste w starszych systemach bankowych, nie mają żadnych separatorów i zależą całkowicie od pozycji kolumn zdefiniowanych w osobnej specyfikacji. A FIX, standard komunikatów, który leży u podstaw handlu elektronicznego, to strumień par tag-wartość z własnym słownikiem, który trzeba sparsować na sensowne pola, zanim da się cokolwiek z nim zrekoncyliować.

Obsługa wszystkich siedmiu oznacza, że użytkownik prawie nigdy nie musi przekształcać swoich eksportów przed porównaniem. Podaje MetiReconowi to, co wypluły jego systemy, a narzędzie czyta pliki w takiej postaci, w jakiej przyszły. Brzmi to jak drobna wygoda. W praktyce usuwa najbardziej żmudny i podatny na błędy krok całego procesu - ręczne przeformatowywanie, które ludzie robią, zanim w ogóle mogą zacząć rekoncyliować.

Projektowanie interfejsu desktopowego

Całą robotę obliczeniową bierze na siebie silnik, ale to interfejs decyduje, czy MetiRecon nadaje się dla kogoś, kto nigdy nie napisał linijki kodu, a projektowanie interfejsu desktopowego ma własne wymagania, których aplikacja webowa nie ma.

Użytkownicy desktopu oczekują, że będą naturalnie pracować z lokalnymi plikami, otwierać kilka okien, używać klawiatury i mieć aplikację, która wygląda, jakby należała do ich systemu, a nie jak strona internetowa w ramce. Flutter dał nam jedną bazę kodu, która mogła to dostarczyć na macOS i Windowsie, wciąż respektując konwencje każdego z nich. Główny przepływ pracy jest celowo liniowy: wybierz dwa pliki, powiedz narzędziu, które pola identyfikują rekord, a które mają być porównane, uruchom rekoncyliację, a potem przeglądaj wynik.

Przeglądanie wyniku to miejsce, w które włożyliśmy dużo uwagi. Po przebiegu użytkownik widzi rekordy dopasowane, rekordy niedopasowane z każdej strony i sklasyfikowane różnice, i może filtrować oraz wchodzić w każdą z tych grup. Profesjonalista finansowy nie chce surowego zrzutu różnic; chce odpowiadać na pytania. Które pozycje są niedopasowane po naszej stronie? Które różnice to tylko zaokrąglenie i można je zignorować? Która pojedyncza transakcja sprawia, że cały ten zestaw się nie spina? Interfejs jest zbudowany wokół szybkiego odpowiadania na te pytania, tak żeby człowiek spędzał czas na ocenie - części, która naprawdę wymaga człowieka - a nie na sortowaniu i szukaniu, które oprogramowanie powinno zrobić za niego.

Raporty gotowe pod audyt

Rekoncyliacja, która żyje tylko na czyimś ekranie, nie jest skończona. Wynik musi stać się dowodem: czymś, co można dołączyć do zamknięcia miesiąca, przekazać przełożonemu albo wyprodukować dla audytora wiele miesięcy później. Poprawne wygenerowanie takiego raportu to osobna funkcja sama w sobie.

Raport gotowy pod audyt ma wymagania, których zwykły eksport nie ma. Musi dokładnie stwierdzać, co zostało porównane - które pliki, które reguły, które tolerancje - tak żeby wniosek dało się odtworzyć. Musi jasno prezentować pozycje dopasowane i niedopasowane, ze sklasyfikowanymi różnicami, tak żeby recenzent widział nie tylko odpowiedź, ale i wyliczenie. I musi być stabilny, tak żeby te same dane wejściowe i te same reguły dawały ten sam raport za każdym razem, bo odtwarzalność to cały sens ścieżki audytu. MetiRecon generuje raporty z tą dyscypliną wbudowaną od środka, co zamienia narzędzie z czegoś, co pomaga wykonać pracę, w coś, co również dowodzi, że praca została wykonana.

Sam format wyniku też ma znaczenie. Raport musi dać się otworzyć i zarchiwizować bez samego MetiReconu, bo audytor sięga po niego czasem długo po tym, jak sprawa została zamknięta. Dlatego wynik jest eksportowany do formatów, które przetrwają - czytelnych dla człowieka i nadających się do wpięcia w dokumentację zamknięcia okresu - a nie zamknięty we wewnętrznym formacie, którego za rok nikt nie będzie miał czym otworzyć. Ta z pozoru nudna decyzja jest częścią tego, co odróżnia narzędzie audytowe od zwykłego eksportu różnic.

Publikacja w Mac App Store

Zbudowanie aplikacji to jedno. Wprowadzenie jej do Mac App Store to osobny projekt z własnymi regułami, i to dokładnie ten etap, na którym początkujący twórcy aplikacji desktopowych utykają.

Aplikacje w Mac App Store działają wewnątrz App Sandbox, granicy bezpieczeństwa, która domyślnie ogranicza to, czego aplikacja może dotknąć. Narzędzie do rekoncyliacji, które czyta pliki wybrane przez użytkownika, musi poprosić dokładnie o właściwe uprawnienia (entitlements), i o nic więcej, bo proszenie o szeroki dostęp jest zarówno ryzykiem przy review, jak i prawdziwym sygnałem problemu z bezpieczeństwem. Poprawne ułożenie modelu dostępu do plików - tak żeby aplikacja mogła otwierać dokumenty, na które użytkownik świadomie ją kieruje, wciąż żyjąc w sandboxie - wymagało realnej staranności.

Na sandboxie siedzi notaryzacja, proces Apple polegający na skanowaniu i podpisywaniu oprogramowania, tak żeby macOS uruchamiał je bez ostrzeżeń, oraz review, które prześwietla wszystko: od tego, jak przedstawiona jest subskrypcja, po to, jak aplikacja zachowuje się przy pierwszym uruchomieniu. Osadzenie natywnej biblioteki w Go wewnątrz aplikacji w sandboxie, po notaryzacji, dystrybuowanej przez sklep, dołożyło własnych zawiłości, bo bibliotekę trzeba poprawnie podpisać i spakować razem z resztą pakietu. Nic z tego nie jest widoczne dla użytkownika, i o to właśnie chodzi: nagrodą za dobre zrobienie tego jest aplikacja, która instaluje się ze sklepu równie gładko jak każda inna, z pełnym zaufaniem systemu.

Warto powiedzieć wprost, dlaczego to właśnie tu projekty grzęzną. Review w Mac App Store bywa odrzucane z powodów, których nie widać z poziomu kodu: metadanych, sposobu przedstawienia subskrypcji, tego, jak aplikacja zachowuje się bez sieci albo przy pierwszym uruchomieniu na czystym koncie. Każde odrzucenie to kolejna runda i kolejne dni oczekiwania. Przejście przez to bez niespodzianek wymaga nie tylko poprawnego builda, ale też wiedzy, czego recenzent szuka - a tę zdobywa się dopiero po kilku wydaniach.

Publikacja w Microsoft Store

Windows powtarza kształt problemu z zupełnie innym zestawem narzędzi, a traktowanie tego jako „ta sama aplikacja, inny przycisk" to sposób, w jaki projekty się opóźniają.

Dystrybucja przez Microsoft Store oznacza pakowanie aplikacji jako MSIX, nowoczesnego formatu instalatora i pakietu Microsoftu, który wnosi własny model tożsamości aplikacji, podpisywania i uprawnień. Koncepcje przypominają te u Apple - jest krok pakowania, krok podpisywania i zestaw zadeklarowanych uprawnień - ale mechanika, narzędzia i tryby awarii są zupełnie inne. Aplikacja podpisana i znotaryzowana idealnie dla macOS wciąż musi zostać spakowana i podpisana od nowa, po windowsowemu, żeby zasłużyć na to samo zaufanie ze strony Microsoft Store i samego Windowsa.

Uruchomienie tego samego silnika w Go i tego samego interfejsu we Flutterze na Windowsie oznaczało też potwierdzenie, że natywna biblioteka ładuje się i zachowuje tam identycznie, że obsługa plików respektuje konwencje Windowsa i że spakowana aplikacja przechodzi własne review sklepu. Zrobienie tego dwa razy, raz dla każdej platformy, to duża część powodu, dla którego dystrybucja desktopowa na wielu platformach jest tym, co niedoświadczone zespoły najczęściej niedoszacowują. Kod, który liczy rekoncyliację, jest wspólny. Pracę nad tym, żeby dało się go wydać, trzeba wykonać dwa razy.

Na Windowsie osobnym tematem jest zaufanie systemu. Nawet poprawnie spakowana aplikacja musi być podpisana w sposób, który Windows i mechanizmy takie jak SmartScreen zaakceptują bez ostrzeżeń straszących użytkownika przy instalacji. Certyfikaty, tożsamość wydawcy i sposób dystrybucji przez sklep składają się na to, czy aplikacja instaluje się gładko, czy wita użytkownika ekranem ostrzeżenia. To znowu praca niewidoczna w samym produkcie, a decydująca o tym, czy ktokolwiek go w ogóle uruchomi.

Jedna binarka na Apple Silicon i Intela

Maki występują w dwóch architekturach procesora, Apple Silicon i Intel, a od profesjonalnej aplikacji oczekuje się natywnego działania na obu. To oczekiwanie sięgnęło aż w głąb silnika w Go.

Na macOS MetiRecon jest wydawany jako binarka uniwersalna, zawierająca natywny kod zarówno dla arm64, jak i x86_64, tak że system uruchamia właściwą wersję automatycznie, a użytkownik nigdy o tym nie myśli. Ponieważ silnik dopasowań to skompilowany Go, a nie kod interpretowany, oznaczało to zbudowanie silnika osobno pod każdą architekturę i połączenie wyników w uniwersalny build, a potem upewnienie się, że warstwa interoperacyjności między Flutterem a Go zachowuje się identycznie na obu. Poprawne zrobienie tego to różnica między aplikacją, która działa natywnie z pełną prędkością na nowoczesnym Macu, a taką, która ledwo się wlecze pod tłumaczeniem - i jest to rodzaj szczegółu, który jest niewidoczny, gdy działa, i rażąco oczywisty, gdy nie.

Subskrypcje rozliczane przez sklepy

MetiRecon to produkt subskrypcyjny, a subskrypcje są rozliczane przez same sklepy, a nie przez jakikolwiek zewnętrzny system płatności. To była świadoma decyzja z realnymi konsekwencjami.

Sprzedaż przez własne rozliczenia sklepu oznacza, że użytkownik płaci kontem, które już ma - tym powiązanym z profilem Apple lub Microsoft - z metodą płatności już zapisaną. Nie ma osobnej kasy, nie ma danych karty do wpisywania w formularz osoby trzeciej, i nie ma zewnętrznej usługi, której trzeba zaufać z danymi płatniczymi. Dla narzędzia, które sprzedaje swoją prywatność jako podstawową wartość, ta spójność ma znaczenie: ta sama aplikacja, która nigdy nie wysyła danych użytkownika, nigdy też nie odsyła go na nieznaną stronę płatności. Jest to też wymóg obu sklepów dla tego rodzaju produktu, więc zbudowanie aplikacji wokół subskrypcji rozliczanych przez sklep, sprawdzania uprawnień i walidacji paragonów od samego początku pozwoliło uniknąć bolesnej przeróbki później. Efekt jest taki, że użytkownik przechodzi od pobrania do płatnej subskrypcji bez ani jednego momentu, w którym musiałby zaufać komukolwiek poza sklepem, w którym już się znajduje.

Testowanie narzędzia, które musi być poprawne

Dla większości aplikacji błąd to irytacja. Dla narzędzia do rekoncyliacji błąd może być błędną liczbą w raporcie finansowym, a to zmienia, jak poważnie trzeba potraktować testowanie.

Poprawność silnika dopasowań była testowana względem znanych danych wejściowych i oczekiwanych wyników, w tym niewygodnych przypadków, które czynią rekoncyliację trudną: różnic z zaokrągleń, różnic czasowych, relacji jeden do wielu, zduplikowanych kluczy, pustych i uszkodzonych plików, nietypowych kodowań i pełnego zakresu obsługiwanych formatów. Poza poprawnością jest zachowanie pod obciążeniem, bo silnik, który jest poprawny na stu wierszach, ale wykłada się na kilku milionach, nie nadaje się do celu. A poza silnikiem jest cała aplikacja, testowana na macOS i Windowsie, na obu architekturach Maca i przez ścieżkę dystrybucji sklepowej, którą faktycznie przechodzą prawdziwi użytkownicy.

To także miejsce, w którym widać, po co jest osobny tester - ktoś, kto nie pisał tego kodu. Deweloper testujący własną logikę rekoncyliacji ma skłonność do sprawdzania przypadków, o których już pomyślał, kiedy ją pisał. Osobna osoba, celowo próbująca zepsuć narzędzie najbrzydszymi plikami, jakie potrafi skonstruować, znajduje przypadki, których deweloper sobie nie wyobraził. Dla produktu, którego cała wartość opiera się na byciu godnym zaufania, ta niezależna kontrola nie jest luksusem.

Dużą częścią tej pracy było wytwarzanie danych testowych. Prawdziwe pliki finansowe są wrażliwe i nie nadają się do trzymania w zestawie testów, więc trzeba było skonstruować syntetyczne zbiory, które odtwarzają wszystkie trudne kształty: zaokrąglenia, różnice kursowe, przesunięcia czasowe, duplikaty i relacje wiele do wielu. Do tego dochodzą testy regresyjne, które pilnują, żeby poprawka jednego przypadku brzegowego nie zepsuła innego - bo w silniku dopasowań zmiany lubią mieć skutki uboczne w miejscach, których nikt się nie spodziewa.

Co ten projekt mówi o tym, jak pracujemy

Działający prototyp rekoncyliacji to weekend dla kompetentnego inżyniera. Wszystko po tym weekendzie jest właściwym projektem: dwa review w sklepach, dwie architektury procesora, dane finansowe, które nie mogą zgubić grosza, i audytor, którego trzeba przekonać. Tam idzie większość kalendarza i tam porzuca się większość produktów.

Sandbox i pakowanie rozstrzygał ktoś, kto przeprowadzał już oprogramowanie przez oba sklepy i wiedział dokładnie, które uprawnienie przesądza o wyniku - dlatego review kosztowało tu dni, a nie tygodnie. Projektant silnika był zarazem jego autorem, a autor świadomie nie był jego testerem: silnik sprawdzany przez osobę, która go napisała, spotyka wyłącznie przypadki, jakie ta osoba wcześniej sobie wyobraziła.

Czytaj to jako dowód drogi, nie domeny. Przenośna jest tu nie rekoncyliacja, tylko przeprowadzenie produktu przez certyfikaty, uprawnienia, notaryzację, pakowanie MSIX i dwa review w sklepach - aż do momentu, w którym obca osoba może go zainstalować i za niego zapłacić. Jeśli Twój produkt musi pokonać tę samą trasę, to jest trasa, którą znamy.

Co powiedzielibyśmy innemu zespołowi

Jeśli rozważasz budowę czegoś w tym kształcie - poważnego narzędzia desktopowego z natywnym rdzeniem obliczeniowym, dystrybuowanego przez sklepy - oto pięć rzeczy, które ustalamy z klientem, zanim ruszy pierwszy sprint.

Każdą z tych rzeczy podnosimy z klientem przed pierwszym sprintem. Produkt desktopowy, który ma się czysto instalować z dwóch sklepów i zadowolić zespół finansowy, zawodzi w przewidywalnych miejscach - a znajomość tych miejsc w pierwszym tygodniu jest warta więcej niż dowolna ilość zapału w ósmym.

Najczęstsze pytania klientów

Czy MetiRecon to Wasz własny produkt, czy zbudowaliście go dla klienta?

To nasz własny. Sami go wymyśliliśmy, zaprojektowaliśmy, zbudowaliśmy i wydaliśmy. Pokazujemy go, bo to dowód tego, co dowozimy: nie demo, tylko prawdziwy produkt w dwóch sklepach, za który ludzie płacą. Dla klientów budujemy tak samo.

Czy zbudowalibyście dla nas taki produkt desktopowy?

Tak. Natywna aplikacja desktopowa na macOS i Windows, z wydajnym silnikiem w rdzeniu, to dokładnie ten rodzaj pracy, którą wykonujemy. Wasz produkt będzie się różnił w szczegółach, ale kształt zadania - projekt, budowa, testy i wydanie w sklepach - jest ten sam, opisany powyżej.

Czy ogarniacie publikację w Mac App Store i Microsoft Store?

Tak, od początku do końca. Sandbox, uprawnienia, notaryzacja, pakowanie MSIX, podpisywanie, wpisy w sklepie i przejście review to część pracy, którą bierzemy na siebie, a nie dodatkowy koszt odkrywany później. To też etap, na którym grzęźnie większość pierwszych wydań, i właśnie dlatego bierzemy go na siebie.

Czy potraficie osadzić szybki silnik napisany w Go, Rust lub C++ wewnątrz aplikacji Flutter?

Tak. MetiRecon uruchamia silnik w Go wewnątrz interfejsu we Flutterze przez natywny interfejs, a to samo podejście działa dla innych języków kompilowanych. Pozwala zachować jeden, wieloplatformowy interfejs, uruchamiając ciężkie obliczenia z natywną prędkością.

Czy to my jesteśmy właścicielem kodu i kont w sklepach?

Tak. Wy jesteście właścicielem kodu, repozytoriów i kont w sklepach. Możemy przekazać wszystko Waszemu wewnętrznemu zespołowi, kiedy tylko zajdzie potrzeba, bez uzależniania od nas.

Ile trwa taki build?

Zależy od zakresu, ale nasze standardowe pierwsze wydanie to osiem tygodni, przy stałej cenie i stałym terminie. Zaczynamy od ustalenia, co wchodzi do tej pierwszej wersji, a potem budujemy w dwutygodniowych iteracjach, z których każda kończy się czymś, co można zainstalować.

Masz produkt, który trzeba zbudować?

Doprowadzamy produkty mobilne i desktopowe od pustego repozytorium do wydania - łącznie z review i sklepami. 30 minut rozmowy wystarczy, żebyśmy powiedzieli, ile realnie zajmie Twój.

Umów rozmowę
← Wróć do wszystkich realizacji