Producent aplikacji webowych - etapy i produkty pracy

Aplikacja webowa pokazuje treści, które zmieniają się z każdym żądaniem, więc nie da się jej zbudować z samych plików statycznych na dysku serwera. Producent musi dostarczyć kod po stronie przeglądarki i serwera oraz bazę danych ze sposobem jej wdrażania. SoftwareStudio oferuje rozwiązania webowe od 2008 roku i obok projektów na zamówienie rozwija gotowe produkty, na przykład platformę do obsługi reklamacji opisaną w artykule RMA system.

Projekt dzieli się na etapy, a każdy kończy się artefaktem, który zamawiający może zaakceptować albo odrzucić. Dzięki temu spory dotyczą konkretnego dokumentu lub działającej funkcji, a nie ogólnego wrażenia, że praca posuwa się wolno. Szerszy opis procesu w przypadku programów biznesowych zawiera tekst o procesie tworzenia aplikacji biznesowych.

EtapProdukt pracyCo zatwierdza zamawiający
Wymaganiaspecyfikacja z kryteriami akceptacjizakres i priorytety funkcji
Prototypmakiety lub klikalny model interfejsuukład ekranów i przepływ pracy
Iteracjedziałający przyrost aplikacji po każdym cykluzgodność funkcji z kryteriami
Testyraport z wynikami testów automatycznych i akceptacyjnychgotowość do wdrożenia
Wdrożeniewydanie na środowisku produkcyjnym z migracją bazyuruchomienie i plan wycofania
Przekazaniedokumentacja techniczna i procedury utrzymaniaodbiór końcowy

Reguła: etap zamyka się dopiero wtedy, gdy jego artefakt został zaakceptowany na piśmie, bo poprawki po odbiorze kosztują kilkakrotnie więcej niż ustalenia na wejściu.

Wymagania i analiza przed pierwszą linią kodu

Praca zaczyna się od zrozumienia procesu, który aplikacja ma obsłużyć. Analityk rozmawia z użytkownikami, przegląda dotychczasowe arkusze i dokumenty oraz spisuje, kto wykonuje jaką czynność i na jakich danych. Wynik nie musi być obszerny. Ważne, aby dla każdej funkcji istniało kryterium, według którego da się stwierdzić, że działa poprawnie. Zakres i zasady takiej analizy omawia artykuł o analizie przedwdrożeniowej, a zagadnienia doradcze przed rozpoczęciem prac opisuje tekst Doradzamy przy tworzeniu systemów IT.

Kryteria akceptacji zapisuje się często w formie scenariuszy, które testerzy i programiści rozumieją tak samo. Notacja Gherkin, dostępna także w wersji polskiej, opisuje sytuację wyjściową oraz oczekiwany wynik działania:

Scenariusz: zapis dokumentu bez kontrahenta
  Zakładając, że użytkownik ma rolę Handlowiec
  Gdy zapisuje dokument bez wybranego kontrahenta
  Wtedy system odrzuca zapis
  I wskazuje pole Kontrahent jako wymagane

Osobną grupę stanowią wymagania pozafunkcjonalne. Nie opisują one, co aplikacja robi, ale w jakich warunkach ma to robić, i często decydują o architekturze bardziej niż lista ekranów. Zapisuje się je w mierzalnej postaci:

  • Wydajność - czas odpowiedzi ekranu przy zadanej liczbie równoczesnych użytkowników.
  • Bezpieczeństwo - metoda logowania, role, ślad audytowy zmian w danych.
  • Dostępność - godziny pracy systemu i dopuszczalne okna serwisowe.
  • Integracje - systemy zewnętrzne, z którymi aplikacja wymienia dane, oraz kierunek wymiany.
Programista siedzący przy biurku z kilkoma monitorami wyświetlającymi kod i dokumentację
Stanowisko wytwórcze - miejsce, w którym kryteria akceptacji zamieniają się w kod i testy.

Prototyp i iteracje

Tanio poprawia się rysunek, drogo gotowy kod, dlatego układ ekranów sprawdza się przed programowaniem. Makieta lub klikalny prototyp pozwala użytkownikom przejść typową ścieżkę pracy i wskazać miejsca, w których brakuje pola albo kroku. Uwagi z takiej sesji trafiają do specyfikacji, zanim programista napisze pierwszy formularz. O dobrych praktykach projektowania interfejsów przeglądarkowych pisze artykuł o projektowaniu aplikacji webowych.

Wytwarzanie prowadzi się w krótkich iteracjach. Na początku cyklu zespół i zamawiający wybierają z listy zadań (backlogu) pozycje na najbliższy okres. Na końcu oglądają działającą wersję na środowisku testowym i decydują, co zmienić. Iteracja nie wydłuża projektu, bo zmiana wymagań wychodzi na jaw po kilku dniach, a nie po miesiącach. Podejście zwinne w projektach na zamówienie opisuje osobny tekst Zwinne zarządzanie projektami IT na zamówienie.

SytuacjaZwykła reakcja zespołuSkutek dla harmonogramu
Nowa funkcja w trakcie iteracjitrafia do backlogu i czeka na kolejny cyklbieżący cykl się nie zmienia
Błąd krytyczny z poprzedniego wydaniaprzerywa bieżące zadania, jest naprawiany od razuprzesunięcie części planowanych pozycji
Zmiana priorytetów zamawiającegozmiana kolejności backlogu na początku następnego cyklukoszt ograniczony do jednego cyklu

Minimalna wersja produktu

Przy nowej aplikacji pierwszym celem bywa MVP, czyli wersja z najmniejszym zestawem funkcji, który daje użytkownikom wartość. Pozwala ona sprawdzić założenia na prawdziwych danych, zanim rozbuduje się wszystkie moduły. Kluczem jest odcięcie tego, co nie jest potrzebne do pierwszego obiegu dokumentu, a nie okrojenie jakości: raz zapisane dane muszą mieć strukturę zdolną znieść kolejne rozszerzenia.

Estymacja i widoczność postępu

Szacowanie pracy w projektach programistycznych obarczone jest niepewnością, więc zespoły przedstawiają je jako zakresy, a nie pojedyncze liczby. Zadania wycenia się względnie, porównując je z już wykonanymi, a po kilku iteracjach zespół zna własne tempo i potrafi przewidzieć, ile pozycji backlogu zmieści się w kolejnym cyklu. Zamawiający widzi postęp jako listę zakończonych funkcji na środowisku testowym, a nie jako procent, który trudno zweryfikować.

Wiarygodny obraz daje też tablica zadań z jasnymi stanami: do zrobienia, w toku, w przeglądzie, w testach, gotowe. Pozycja, która tygodniami tkwi w jednym stanie, sygnalizuje problem wcześniej niż jakikolwiek raport miesięczny.

Struktura kodu i przeglądy zmian

Kod aplikacji ma zwykle kilka warstw, w których każda odpowiada za jedno zadanie: kontrolery przyjmują żądania, usługi realizują reguły biznesowe, a repozytoria komunikują się z bazą. Rozdzielenie ułatwia testowanie, bo regułę biznesową sprawdza się bez uruchamiania serwera i bazy. Poniższy szkic jest poglądowy i pokazuje kontroler cienki jak to możliwe:

[ApiController]
[Route("api/dokumenty")]
public class DokumentyController : ControllerBase
{
    private readonly IDokumentService _uslugi;
    public DokumentyController(IDokumentService uslugi) => _uslugi = uslugi;

    [HttpPost]
    public async Task<IActionResult> Zapisz(DokumentDto dto)
    {
        var wynik = await _uslugi.ZapiszAsync(dto);
        return wynik.Sukces ? Ok(wynik.Id) : BadRequest(wynik.Bledy);
    }
}

Zmiany trafiają do wspólnego repozytorium przez gałęzie i żądania scalenia. Zanim kod wejdzie do głównej gałęzi, przechodzi przegląd drugiej osoby oraz automatyczne kontrole. Przegląd wychwytuje błędy logiczne i niespójności z architekturą, a nie tylko literówki, i rozprzestrzenia wiedzę o kodzie w zespole.

KontrolaMomentCo wychwytuje
Analiza statycznaprzy każdym zapisie w repozytoriumniebezpieczne konstrukcje, nieużywany kod
Przegląd koduprzed scaleniem gałęzibłędy w regułach i naruszenia architektury
Testy jednostkoweprzy każdym budowaniuregresję w logice biznesowej

Dobór technologii do wymagań

Technologię dobiera się do projektu, a nie odwrotnie. SoftwareStudio podaje w ofercie ASP.NET i SQL Server po stronie serwera oraz JavaScript z TypeScriptem i biblioteką React po stronie przeglądarki, a wybór ostatecznego zestawu zależy od wymagań i skali systemu. W praktyce rozstrzygają takie pytania jak to, czy zespół zamawiającego będzie utrzymywał kod, jakie środowisko serwerowe już działa w firmie i czy integracje wymagają konkretnych bibliotek.

Testy automatyczne w projekcie

Testy dzieli się według zakresu. Testy jednostkowe sprawdzają pojedynczą regułę, na przykład wyliczenie statusu dokumentu, i wykonują się w ułamkach sekundy. Testy integracyjne uruchamiają część aplikacji razem z prawdziwą bazą i wychwytują błędy w zapytaniach lub konfiguracji. Testy akceptacyjne odtwarzają scenariusze użytkownika i są najwolniejsze, dlatego obejmują tylko najważniejsze ścieżki. Ogólny opis narzędzi dla platformy .NET zawiera dokumentacja testowania w .NET.

public class StatusDokumentuTests
{
    [Theory]
    [InlineData(0, DokumentStatus.Nowy)]
    [InlineData(5, DokumentStatus.Zatwierdzony)]
    public void Status_zalezy_od_liczby_zatwierdzen(int zatwierdzenia, DokumentStatus oczekiwany)
    {
        var dokument = new Dokument { Zatwierdzenia = zatwierdzenia };
        Assert.Equal(oczekiwany, dokument.ObliczStatus());
    }
}

Testy integracyjne aplikacji ASP.NET Core uruchamiają serwer w pamięci i wysyłają do niego żądania HTTP, co opisuje dokumentacja testów integracyjnych w ASP.NET Core. Sprawdzają one cały tor od kontrolera do bazy, a więc także konfigurację wstrzykiwania zależności i uwierzytelniania, której testy jednostkowe nie dotykają.

Reguła: błąd znaleziony na produkcji dostaje najpierw test, który go odtwarza, i dopiero potem poprawkę - dzięki temu ten sam defekt nie wraca w kolejnym wydaniu.

Zespół w przestrzeni biurowej przed dużym ekranem z kodem i wizualizacją interfejsu aplikacji
Przegląd wyników iteracji - zespół i zamawiający oceniają działający przyrost aplikacji.

Potok CI/CD i wdrożenie

Ciągła integracja (CI) polega na tym, że każda zmiana w repozytorium uruchamia automatyczne zbudowanie aplikacji i wykonanie testów. Ciągłe dostarczanie (CD) rozszerza ten potok o wdrożenie gotowego pakietu na kolejne środowiska. Człowiek zatwierdza tylko przejście na produkcję, a nie ręcznie kopiuje pliki, co usuwa jedną z częstszych przyczyn awarii: pomyłkę w kroku wykonywanym z pamięci.

# przykład w składni Azure Pipelines, kroki poglądowe
trigger:
  branches:
    include: [ main ]

pool:
  vmImage: 'windows-latest'

steps:
- script: dotnet build --configuration Release
  displayName: 'Budowanie'
- script: dotnet test --configuration Release --no-build
  displayName: 'Testy jednostkowe i integracyjne'
- script: dotnet publish --configuration Release --output $(Build.ArtifactStagingDirectory)
  displayName: 'Pakiet do wdrożenia'
- publish: $(Build.ArtifactStagingDirectory)
  artifact: aplikacja

Ten sam pakiet trafia kolejno na środowisko testowe i produkcyjne, a różnice między nimi zawiera wyłącznie konfiguracja, taka jak ciąg połączenia z bazą. Budowanie osobnego pliku dla każdego środowiska rodzi ryzyko, że na produkcję trafi coś, czego nikt nie testował. Podstawy potoków opisuje dokumentacja Azure Pipelines, choć ta sama zasada obowiązuje w innych narzędziach.

Liczba środowisk zależy od projektu. Środowisko deweloperskie służy programistom, testowe zamawiającemu do akceptacji, a produkcyjne użytkownikom. Środowisko testowe powinno mieć tę samą wersję bazy i podobny rozmiar danych co produkcja, ponieważ zapytanie szybkie na kilku rekordach potrafi zwolnić na milionie wierszy. Dane osobowe kopiowane z produkcji do testów wymagają anonimizacji, aby nie rozszerzać dostępu do nich poza uprawniony krąg.

Migracje bazy i wycofanie wydania

Najtrudniejszą częścią wdrożenia jest baza danych, bo w odróżnieniu od plików aplikacji nie da się jej po prostu podmienić. Zmiany schematu zapisuje się jako numerowane skrypty migracji, uruchamiane w potoku przed przełączeniem ruchu. Wycofanie aplikacji do poprzedniej wersji jest możliwe tylko wtedy, gdy schemat po migracji nadal ją obsługuje, dlatego usuwanie kolumn odkłada się o jedno wydanie.

Zmiana schematuBezpieczna kolejność
Nowa kolumnadodać z wartością domyślną, potem wdrożyć aplikację, która jej używa
Zmiana nazwy kolumnydodać nową kolumnę, przenieść dane, w kolejnym wydaniu usunąć starą
Usunięcie kolumnynajpierw wdrożyć wersję niekorzystającą z kolumny, potem ją usunąć

Bezpieczeństwo w cyklu wytwórczym

Bezpieczeństwo aplikacji zaczyna się na etapie wymagań, gdy zespół ustala, jakie dane są wrażliwe, kto ma do nich dostęp i co zapisuje ślad audytowy. W dalszych etapach kontrole wbudowuje się w potok, żeby uruchamiały się przy każdej zmianie. Analiza zależności wyszukuje w bibliotekach znane podatności, a przegląd kodu korzysta z listy kontrolnej opartej na zestawieniu OWASP Top Ten.

- script: dotnet list package --vulnerable --include-transitive
  displayName: 'Podatne pakiety NuGet'

Osobną zasadą jest oddzielenie sekretów od kodu. Hasła do bazy i klucze do zewnętrznych usług nie trafiają do repozytorium, tylko do magazynu sekretów środowiska, z którego aplikacja pobiera je podczas uruchomienia. Repozytorium z hasłem wpisanym w pliku konfiguracyjnym pozostaje zagrożeniem nawet po jego usunięciu, bo historia zmian zachowuje starą wartość.

Reguła: sekret, który choć raz trafił do repozytorium, uznaje się za ujawniony i wymienia, a samo usunięcie pliku nie wystarcza.

Wydanie i przekazanie do utrzymania

Każde wydanie dostaje numer wersji i opis zmian dla zamawiającego. Schemat semantyczny, opisany na stronie semver.org, zapisuje numer jako trzy liczby oddzielone kropkami: zmiana pierwszej oznacza niezgodność wsteczną, drugiej nową funkcję, a trzeciej poprawkę błędu. Numer w stopce aplikacji ułatwia zgłaszanie błędów, bo zespół od razu wie, której wersji dotyczą.

Przekazanie zamyka projekt i otwiera fazę utrzymania. Zamawiający otrzymuje dokumentację architektury, opis środowisk, procedurę odtworzenia bazy oraz listę kont technicznych. Kto przejmuje aplikację po wdrożeniu, jakie ma obowiązki i jak mierzy się jakość usługi, opisuje artykuł o utrzymaniu dedykowanych aplikacji webowych. Zagadnienia samej budowy warstw i sesji przedstawia tekst o aplikacji webowej od przeglądarki po bazę danych, a kwestię zaangażowania zespołów po stronie klienta omawia materiał o roli zespołów wdrożeniowych.

Ofertę projektów na zamówienie producent opisuje na stronie oprogramowanie na zamówienie w SoftwareStudio.