Aplikacja internetowa działa w przeglądarce, a jej dane leżą na serwerze firmy albo u dostawcy usługi. Ten tekst opisuje najczęstsze zastosowania w firmach: portal klienta i panel zamówień, a także raporty. Dla każdego pokazano, co widzi użytkownik, jakie zapytanie idzie do serwera i co odpowiada baza SQL Server.

Cykl żądanie-odpowiedź w HTTP oraz sesje omawia osobny artykuł o aplikacji webowej. Tutaj nacisk pada na przykłady zastosowań i na podział odpowiedzialności między przeglądarką a serwerem z bazą danych.

Aplikacje internetowe w firmie - przykłady zastosowań

Ta sama technologia obsługuje bardzo różne role. Różnicę wyznacza to, kto korzysta z aplikacji i do których rekordów ma dostęp. Portal klienta widzi kontrahent zewnętrzny, panel zamówień obsługuje pracownik, a raporty przeglądają kierownicy.

ZastosowanieKto korzystaTypowa operacjaDane w bazie
Portal klientaKontrahent zewnętrznyPodgląd zamówień i dokumentówWidoki tylko do odczytu, zawężone do jednego kontrahenta
Panel zamówieńPracownik obsługiZakładanie zamówień i zmiana statusówTabele zamówień z historią zmian
RaportyKierownik lub dyrektorFiltrowanie i eksport zestawieńWidoki i procedury agregujące

Reguła: każde zastosowanie ma osobny zestaw uprawnień, bo konto kontrahenta nie może sięgać do tabel używanych przez pracownika.

Wszystkie trzy zastosowania mogą działać na jednej bazie i jednym serwerze aplikacji. Rozdzielają je role użytkowników oraz zestaw procedur udostępnianych każdej z nich. Dzięki temu nowe zastosowanie dodaje się bez przebudowy pozostałych.

Portal klienta - dostęp do własnych danych

Portal klienta zastępuje telefony i wiadomości z pytaniem o status przesyłki lub dokument. Kontrahent loguje się na własne konto i widzi zamówienia i faktury, a także listę dokumentów do pobrania. Pracownicy obsługi nie odbierają wtedy pytań, na które odpowiedź już jest w bazie.

Laptop i dwa smartfony z oknami aplikacji internetowej, ilustracja dostępu do jednego systemu z różnych urządzeń
Portal klienta działa w przeglądarce na komputerze i telefonie, bez instalacji na urządzeniu kontrahenta

Tożsamość kontrahenta w sesji

Najczęstszy błąd portali polega na tym, że numer kontrahenta jest częścią adresu, na przykład /zamowienia/klient/118. Zmiana liczby w pasku adresu pokazuje wtedy cudze rekordy. Poprawna wersja bierze identyfikator z sesji zapisanej na serwerze po zalogowaniu i ignoruje wszystko, co przyszło w adresie.

Zakres danych widocznych w portalu

Portal pokazuje wybrane kolumny, a nie całe tabele. Cena zakupu, marża czy uwagi wewnętrzne pozostają w bazie i nie trafiają do odpowiedzi API. Przy awizacjach dostaw ten sam wzorzec stosuje się do przewoźników, którzy widzą wyłącznie własne okna czasowe, co opisuje artykuł o module awizacji dla przewoźników.

  • Logowanie - konto kontrahenta jest przypisane do jednego rekordu w tabeli kontrahentów, a hasło przechowuje się wyłącznie jako skrót.
  • Zakres danych - każde zapytanie API dostaje identyfikator kontrahenta z sesji jako parametr, nigdy z formularza.
  • Dokumenty - pliki PDF pobiera się przez adres z krótkim czasem ważności, a nie przez publiczny katalog.
  • Powiadomienia - zmianę statusu zamówienia zapisuje się w kolejce, a wysyłką wiadomości zajmuje się osobne zadanie.

Panel zamówień - jeden ekran dla obsługi

Panel zamówień służy pracownikom, którzy zakładają i poprawiają zamówienia oraz zmieniają ich status. Lista zamówień filtruje się na serwerze, nie w przeglądarce, bo baza przy kilkudziesięciu tysiącach rekordów odda stronę wyników szybciej niż przeglądarka przefiltruje pełny zestaw. Paginację zapewnia klauzula OFFSET ... FETCH z sortowaniem po indeksowanej kolumnie.

SELECT z.NumerZamowienia, z.DataZlozenia, z.Status
FROM dbo.Zamowienie AS z
WHERE z.KlientId = @KlientId
ORDER BY z.DataZlozenia DESC, z.ZamowienieId DESC
OFFSET @Pominiete ROWS FETCH NEXT 50 ROWS ONLY;

Drugie kryterium sortowania, unikalny identyfikator, jest potrzebne dla powtarzalności wyników. Bez niego dwa zamówienia z tą samą datą mogą zamienić się miejscami między stronami i jedno z nich zniknie z listy albo pojawi się dwa razy.

StatusKto go ustawiaSkutek w bazie
NoweKlient przez portal lub pracownikNowy rekord zamówienia i wpis w historii
PotwierdzonePracownik obsługiRezerwacja ilości towaru
W realizacjiMagazynPowiązanie z dokumentem wydania
ZrealizowaneSystem po zamknięciu dokumentuZamknięcie rezerwacji, blokada edycji
AnulowanePracownik, do momentu wydaniaZwolnienie rezerwacji

Zestaw statusów w tabeli jest przykładowy. Rzeczywisty zależy od procesu firmy, ale zasada pozostaje ta sama: każdy status ma jednoznacznego wykonawcę i konkretny skutek w danych.

Zmiana statusu w jednej transakcji

Dwóch pracowników może otworzyć to samo zamówienie i zmienić je prawie jednocześnie. Kolumna typu rowversion pozwala wykryć taką sytuację bez blokowania rekordu na czas edycji. Aplikacja odczytuje wersję razem z zamówieniem, a przy zapisie porównuje ją z aktualną. Poniższa procedura jest przykładem, nie schematem konkretnego produktu.

BEGIN TRANSACTION;

UPDATE dbo.Zamowienie
SET Status = N'w realizacji'
WHERE ZamowienieId = @ZamowienieId
  AND Wersja = @Wersja;            -- kolumna rowversion odczytana wraz z rekordem

IF @@ROWCOUNT = 0
BEGIN
    ROLLBACK TRANSACTION;
    THROW 50001, N'Zamówienie zmienił inny użytkownik.', 1;
END;

INSERT INTO dbo.ZamowienieHistoria (ZamowienieId, Status, Uzytkownik, Kiedy)
VALUES (@ZamowienieId, N'w realizacji', @Uzytkownik, SYSDATETIME());

COMMIT TRANSACTION;

Reguła: zmiana statusu i wpis do historii wykonują się w jednej transakcji, bo osobne zapisy zostawiłyby historię niezgodną ze stanem zamówienia.

Użytkownik, któremu zapis się nie udał, dostaje komunikat z prośbą o odświeżenie widoku. Nie traci pracy po cichu, a baza nie trzyma blokady przez czas, gdy ktoś wypełnia formularz.

Raporty w przeglądarce - zapytania do bazy SQL

Raport w aplikacji internetowej to zapytanie z parametrami, którego wynik przeglądarka wyświetla jako tabelę lub wykres. Filtry z ekranu, na przykład zakres dat, trafiają do serwera jako parametry procedury. Warunek dat warto zapisać jako przedział otwarty z prawej strony, bo wtedy indeks na kolumnie daty daje się wykorzystać.

SELECT CAST(z.DataZlozenia AS date) AS Dzien,
       COUNT(*) AS LiczbaZamowien,
       SUM(CASE WHEN z.Status = N'anulowane' THEN 1 ELSE 0 END) AS Anulowane
FROM dbo.Zamowienie AS z
WHERE z.DataZlozenia >= @Od
  AND z.DataZlozenia < @Do
GROUP BY CAST(z.DataZlozenia AS date)
ORDER BY Dzien;

Funkcja CAST stoi tu w liście kolumn i w grupowaniu, ale nie w warunku WHERE. Gdyby stała w warunku, silnik musiałby przeliczyć każdy wiersz i pominąć indeks.

Raporty, które trwają dłużej niż kilka sekund, nie powinny blokować zapisów z panelu zamówień. Dwa rozwiązania to włączenie w bazie opcji READ_COMMITTED_SNAPSHOT, dzięki której odczyt korzysta z wersji wierszy zamiast blokad, oraz osobna kopia do odczytu. Zestawienia wydrukowane na papierze albo w PDF wygodnie budować w SQL Report Builder, a ich publikację w przeglądarce omawia artykuł o Reporting Services.

Eksport i raporty liczone długo

Eksport do pliku CSV lub XLSX buduje się na serwerze i zwraca jako strumień, z górnym limitem wierszy. Przy zestawieniu, które liczy się dłużej niż kilkanaście sekund, przeglądarka nie powinna czekać na odpowiedź. Serwer zapisuje zlecenie raportu w tabeli, osobne zadanie liczy wynik, a ekran co kilka sekund pyta o status i na końcu podaje odnośnik do gotowego pliku.

Taki układ chroni przed przekroczeniem limitu czasu połączenia i pozwala ograniczyć liczbę raportów liczonych jednocześnie. Kolejka zleceń jest zwykłą tabelą z kolumnami statusu i daty rozpoczęcia, więc administrator widzi w niej, który raport zajmuje serwer najdłużej.

Architektura aplikacji - przeglądarka i serwer z bazą SQL

Aplikacja z powyższych przykładów składa się z trzech warstw, a każda odpowiada za inny rodzaj decyzji. Pomyłka polega zwykle na tym, że reguła trafia do niewłaściwej warstwy, na przykład walidacja uprawnień do przeglądarki.

WarstwaOdpowiada zaCzego unikać
Przeglądarka (JavaScript)Wyświetlanie, wstępna walidacja pól, wywołania AJAXZaufania do walidacji i do ukrytych pól formularza
API (C#)Uwierzytelnianie, autoryzacja, reguły biznesowe, transakcjeSkładania SQL z tekstu przysłanego przez użytkownika
Baza SQL ServerDane, klucze obce, ograniczenia, indeksyLogiki zależnej od tego, kto kliknął

Kontrakt API między przeglądarką a bazą

Interfejs API tłumaczy adres i parametry na wywołanie procedury składowanej. Poniższy fragment w C# pokazuje punkt końcowy, który zwraca zamówienia kontrahenta z sesji. Identyfikator klienta pochodzi z tożsamości użytkownika, a parametry przekazuje się przez kolekcję Parameters. Dokumentację tego typu interfejsów w ASP.NET Core zawiera Microsoft Learn.

[HttpGet("api/zamowienia")]
public IActionResult Zamowienia(string status = null)
{
    int klientId = BiezacyKlientId();   // metoda przykładowa, odczyt z tożsamości użytkownika
    var wynik = new List<object>();

    using var po = new SqlConnection(_connectionString);
    using var cmd = new SqlCommand("dbo.ZamowieniaKlienta", po)
    {
        CommandType = CommandType.StoredProcedure
    };
    cmd.Parameters.Add("@KlientId", SqlDbType.Int).Value = klientId;
    cmd.Parameters.Add("@Status", SqlDbType.NVarChar, 20).Value = (object)status ?? DBNull.Value;

    po.Open();
    using var r = cmd.ExecuteReader();
    while (r.Read())
    {
        wynik.Add(new { Numer = r.GetString(0), Data = r.GetDateTime(1), Status = r.GetString(2) });
    }
    return Ok(wynik);
}

Po stronie przeglądarki wywołanie sprowadza się do jednego zapytania AJAX. Kod 401 oznacza wygasłą sesję i przekierowuje na ekran logowania, a pozostałe błędy pokazują komunikat bez szczegółów technicznych.

$.getJSON('/api/zamowienia', { status: 'otwarte' })
  .done(function (dane) {
      $('#lista-zamowien').html(dane.map(function (z) {
          return '<tr><td>' + $('<i>').text(z.numer).html() + '</td></tr>';
      }).join(''));
  })
  .fail(function (xhr) {
      if (xhr.status === 401) { window.location = '/logowanie'; }
  });

Obsługa błędów i dziennik zdarzeń

Błąd w API trafia do dziennika z numerem zgłoszenia, a użytkownik dostaje tylko ten numer i krótki komunikat. Szczegóły, takie jak treść wyjątku i parametry wywołania, zostają po stronie serwera. Dzięki numerowi pracownik pomocy technicznej znajduje wpis w dzienniku bez pytania użytkownika o zrzut ekranu.

Indeks pod zapytanie z portalu

Procedura wybiera zamówienia jednego kontrahenta posortowane od najnowszych. Indeks nieklastrowy na parze kolumn KlientId i DataZlozenia pokrywa to zapytanie, a kolumny dołączone klauzulą INCLUDE oszczędzają odczyt z tabeli. Składnię opisuje dokumentacja CREATE INDEX.

CREATE NONCLUSTERED INDEX IX_Zamowienie_Klient_Data
ON dbo.Zamowienie (KlientId, DataZlozenia DESC)
INCLUDE (NumerZamowienia, Status);
Ilustracja bazy danych z walcami tabel, wykresami i chmurą, połączonymi liniami przepływu informacji
Baza SQL Server jako wspólne źródło danych dla portalu klienta i panelu zamówień

Uprawnienia użytkowników zewnętrznych i wewnętrznych

Konto techniczne, którym API łączy się z bazą, powinno mieć uprawnienia tylko do wykonywania procedur z wybranego schematu. Nie dostaje prawa odczytu tabel ani prawa do zmiany struktury. Wtedy błąd w kodzie API nie otwiera dostępu do pozostałych danych.

CREATE SCHEMA portal;
GO
CREATE ROLE rola_portalu;
GRANT EXECUTE ON SCHEMA::portal TO rola_portalu;
DENY SELECT ON SCHEMA::dbo TO rola_portalu;

Drugą warstwę ochrony może dodać mechanizm zabezpieczeń na poziomie wierszy, dostępny od SQL Server 2016. Silnik bazy dopisuje wtedy do każdego zapytania predykat filtrujący, więc pominięcie warunku w kodzie aplikacji nie ujawnia cudzych rekordów.

Podział na role obejmuje też integracje. Wymianę danych z innymi systemami firmy, na przykład z ERP, porządkuje artykuł o integrowaniu aplikacji webowych, który porównuje wywołania REST z kolejkami komunikatów.

Przykłady z produktów SoftwareStudio

SoftwareStudio z Poznania buduje aplikacje internetowe na Microsoft IIS i SQL Server. Systemy takie jak Studio WMS.net i Studio RMA.net działają w przeglądarce, co eliminuje instalację na stanowiskach pracowników. Obsługa rejestru reklamacji jest przykładem panelu, w którym status zgłoszenia przechodzi przez kolejne etapy, a każda zmiana zostaje zapisana w historii.

Dla magazynu aplikacje przeglądarkowe uzupełniają programy na terminale z systemem Android, opisane w artykule o aplikacji magazynowej na Androida. W przeszłości firma tworzyła także programy na kolektory danych. Wycenę aplikacji na zamówienie SoftwareStudio przygotowuje indywidualnie, a rozmowę o zakresie można zacząć w serwisie produktowym Studio VSS.net albo w demo online.

Wybór pierwszego zastosowania do wdrożenia

Pierwsza wersja nie musi obejmować wszystkich trzech zastosowań. Zwykle rozpoczyna się od tego, które ma najprostsze dane i najwyraźniejszy koszt bieżącej obsługi. Prosty projekt w postaci MVP trwa krócej niż rozbudowany system; harmonogram ustala się po analizie wymagań.

  • Liczba użytkowników - portal dla kilkuset kontrahentów obciąża serwer inaczej niż panel dla pięciu pracowników.
  • Wrażliwość danych - dane dostępne dla kontrahentów wymagają dokładniejszych testów uprawnień niż raporty wewnętrzne.
  • Zależność od integracji - panel zamówień bez dostępu do kartoteki towarów z ERP działa na danych przepisywanych ręcznie.
  • Mierzalność efektu - liczba telefonów o status zamówienia daje się policzyć przed wdrożeniem i po nim.

Przebieg pracy od analizy po odbiór opisuje artykuł o etapach tworzenia aplikacji internetowej, a pojęcie aplikacji projektowanej pod jeden proces omawia tekst o dedykowanych aplikacjach webowych. Warunkiem dobrego wyboru jest liczba, którą można porównać po uruchomieniu. Bez niej nie da się ocenić, czy portal odciążył obsługę.