Przejdź do treści

Moduły, biblioteki i bazy danych

Zakres weryfikacji

Treść zachowana z obecnego źródła. Publikacja dokumentacji nie oznacza ponownego potwierdzenia wszystkich komend, przykładów i deklaracji możliwości.

Poziom: średniozaawansowany i ekspert. Początkujący może bezpiecznie uruchomić gotowy projekt referencyjny, ale utworzenie nowego adaptera storage wymaga dziś pracy średniozaawansowanej.

Projekt Junexis może składać się z wielu plików .jnx i lokalnych bibliotek. Ten sam kontrakt łączy kod domenowy z pamięcią procesu, SQLite albo PostgreSQL po stronie @server i przekazuje stan do @report. Pełny pion restartu jest zweryfikowany dla SQLite. PostgreSQL przechodzi kompilację i test kontraktu bez serwera; jego sieciowy E2E czeka na dostępny endpoint testowy.

Jeżeli chcesz od razu zbudować własny projekt, przejdź do praktycznego przewodnika pracy z modułami i bazą danych. Ta strona wyjaśnia model i granice odpowiedzialności.

Pięć pojęć, których nie należy mieszać

Pojęcie Znaczenie w Junexis Przykład
kontrakt domenowy operacje potrzebne systemowi, niezależne od technologii HomeStore.save, HomeStore.snapshot
extern storage deklaracja granicy I/O i jej efektów dla kompilatora zapis, odczyt, transakcja, health check
provider implementacja wspólnej semantyki przechowywania memory, SQLite, PostgreSQL
adapter autora mapowanie typów domenowych i konfiguracji na API providera user/server/home_store.go
binding decyzja uruchomieniowa, którego providera i ustawień użyć memory w teście, SQLite z data/home.db lokalnie

Provider nie powinien znać pojęć domu, pomiaru ani użytkownika. Adapter zna domenę i provider, ale pozostaje za granicą @server. Task zna wyłącznie kontrakt domenowy. Dzięki temu można wymienić technologię bez przepisywania reguł systemu.

Provider nie należy do @server

@server określa miejsce wykonania tasku. Nie oznacza Go, C#, Javy ani Pythona i nie może być właścicielem sterownika bazy. Obecne generowane pakiety Go pozostają działającym rozwiązaniem przejściowym, ale po przeglądzie architektury nie są już modelem docelowym.

Nowy model rozdziela sześć pojęć:

Pojęcie Rola
deklaracja storage typowane operacje i ich efekty
provider technologia, np. PostgreSQL
driver biblioteka dostawcy, np. Npgsql
Provider Runtime niezależny host wykonujący provider
binding jawny wybór providera i konfiguracji środowiska
środowisko docelowe miejsce wykonania tasku korzystającego z zasobu
flowchart LR
    TASK["task w obsługiwanym środowisku"] --> STORAGE["deklaracja storage"]
    STORAGE --> BINDING["binding deploymentu"]
    BINDING --> ABI["Storage Runtime ABI"]
    ABI --> RUNTIME["niezależny Provider Runtime"]
    RUNTIME --> DRIVER["driver dostawcy"]
    DRIVER --> DATABASE[("baza danych")]

Techniczne ABI runtime nie zastępuje deklaracji protocol. protocol nadal opisuje komunikację logicznych części systemu. Granica Provider Runtime będzie widoczna w deployment, diagnostyce i Narrative, ale aplikacja nie będzie udawała, że zapytanie do bazy jest metodą HTTP.

Pierwszy referencyjny host powstanie jako niezależny runtime .NET. Memory, SQLite i PostgreSQL zostaną do niego przeniesione dopiero po ustanowieniu manifestu pakietu, bindingu, lockfile i wspólnej bramki zgodności.

flowchart LR
    DOMAIN["moduły domenowe .jnx"] --> SERVICE["zadania @server"]
    SERVICE --> CONTRACT["extern storage / storage.Store"]
    CONTRACT --> MEMORY["przejściowy Go: memory"]
    CONTRACT --> SQLITE["przejściowy Go: SQLite"]
    CONTRACT --> POSTGRESQL["przejściowy Go: PostgreSQL"]
    SQLITE --> RESTART["restart procesu"]
    POSTGRESQL --> REMOTE["zdalny serwer bazy"]
    RESTART --> SERVICE
    SERVICE --> PANEL["typowany @report"]

Uproszczony wzorzec modelu storage

Gdy skupiamy się wyłącznie na przepływie związanym z bazą danych, przyjmujemy następujący uproszczony wzorzec:

flowchart LR
    EMBEDDED["@embedded<br/>generuje dane"] -->|"typowane komunikaty"| SERVER["@server<br/>zapisuje i odczytuje storage"]
    SERVER --> DB[("SQLite lub PostgreSQL")]
    DB --> SERVER
    SERVER -->|"typowana projekcja"| REPORT["@report<br/>prezentuje dane"]
    REPORT -.->|"intencja użytkownika"| SERVER

W tym wzorcu:

  • @embedded dostarcza dane do systemu,
  • @server jest jedynym właścicielem dostępu do bazy,
  • @report otrzymuje przygotowany wynik i prezentuje go człowiekowi,
  • działanie użytkownika w @report wraca do @server jako typowana intencja,
  • dopiero zadanie @server może autoryzować intencję i zmienić trwały stan.
@embedded → @server → storage → @server → @report
                         ▲                    │
                         └── intencja ────────┘

Jest to wzorzec dydaktyczny dotyczący storage, a nie pełna definicja ról platform. @embedded może również podejmować lokalne decyzje i sterować urządzeniem, a @report może inicjować działania użytkownika. W obecnie zweryfikowanym pionie żadna z tych platform nie otwiera jednak bezpośrednio serwerowego pliku SQLite.

Moduł to nie tekstowe include

Import rozwiązuje symbole i zachowuje pochodzenie diagnostyki. Nie wkleja tekstu jednego pliku do drugiego.

import home::domain
import home::persistence as persistence
import home::reports { HomeObservation, BuildHomeStatus as BuildStatus }

Obsługiwane są importy całego modułu, alias modułu, wybór symboli i alias symbolu. Kompilator wykrywa brak modułu, brak symbolu, kolizję, cykl, różnicę wielkości liter i próbę wyjścia poza dozwolone katalogi projektu. LSP przechodzi do definicji, wyszukuje referencje i wykonuje rename między plikami.

Projekt może również wskazać namespacowaną bibliotekę lokalną. Build zapisuje deterministyczny plik blokady junexis.modules.lock/0.1 z listą modułów, ścieżkami i sumami SHA-256. Publiczny rejestr zdalnych pakietów nie jest jeszcze częścią tego kontraktu.

W codziennej pracy oznacza to, że deweloper:

  1. ustala sourceRoots w junexis.project.toml,
  2. traktuje ścieżkę pliku jako tożsamość modułu,
  3. importuje tylko potrzebne symbole albo nadaje modułowi alias,
  4. pozostawia pipeline, protocol i deployment w module wejściowym,
  5. kontroluje wynik w out/debug/junexis.modules.lock.json.

Poznaj: ten sam system bez trwałych danych

Provider pamięciowy realizuje ten sam interfejs operacji co SQLite. Nadaje się do nauki i szybkiego eksperymentu, ponieważ nowa instancja zaczyna pusta, a zamknięcie świadomie usuwa jej stan.

Nie jest udawanym testem trwałości. Pozwala zobaczyć znaczenie zapisu, odczytu, listowania, usuwania, commitu, rollbacku, anulowania i lifecycle bez tworzenia pliku bazy.

Gotowy projekt referencyjny zawiera adapter wybierający provider, dlatego początkujący może porównać oba zachowania. W nowym projekcie taki adapter nie jest jeszcze generowany automatycznie.

Eksperymentuj: podmień provider na SQLite

SQLite działa w procesie serwera. Panel nie otwiera pliku bazy i nie zna SQL. Otrzymuje typowany wynik z zadania @server.

intencja użytkownika
    → zadanie @server
    → kontrakt domenowy storage
    → parametryzowana operacja SQLite
    → wynik domenowy
    → kanał do @report

Referencyjny provider:

  • nie wymaga CGO ani lokalnego kompilatora C,
  • stosuje parametryzowane zapytania,
  • ma ograniczony timeout blokady i operacji,
  • obsługuje jawny commit i rollback,
  • uruchamia uporządkowane migracje,
  • zapisuje SHA-256 zastosowanej migracji i odrzuca jej późniejszą zmianę,
  • rozróżnia m.in. not-found, conflict, busy, timeout, cancelled i unknown-outcome.

Przejmij kontrolę: jawna granica adaptera

Aktualny pion nie jest ORM-em. Deklaracja extern storage opisuje operacje, a adapter domenowy autora mapuje typy Junexis na rekordy wspólnego interfejsu storage.Store. Dzięki temu wybór memory albo SQLite nie zmienia tasków domenowych.

W bieżącej składni każda operacja extern storage wywoływana z tasku wymaga również odpowiadającej deklaracji extern task. Plik Go w user/server jest własnością dewelopera. junexis extern scaffold nie nadpisuje tego pliku, ale obecnie skanuje tylko moduł wejściowy, nie importowany moduł storage. Przewodnik pokazuje oba warianty pracy bez ukrywania tego ograniczenia.

Ta jawność jest dziś zaletą eksperta i zarazem granicą produktu. Następnym krokiem będzie automatyczny binding providera, jego lifecycle i generowanie typowanego adaptera, tak aby początkujący mógł dodać lokalną bazę bez ręcznego kodu Go. Docelowa składnia resource pozostaje projektem; nie jest jeszcze częścią gramatyki języka.

Co zostało sprawdzone od początku do końca

Referencyjny system składa się z pięciu modułów. Próba wykonuje kolejno:

  1. sprawdzenie grafu importów i semantyki,
  2. zbudowanie serwera, panelu, migracji i blokady modułów,
  3. zapis obserwacji przez zadanie serwerowe,
  4. kontrolowany rollback bez pozostawienia rekordu,
  5. zakończenie procesu,
  6. uruchomienie drugiego procesu na tej samej bazie,
  7. odczyt zatwierdzonego rekordu,
  8. przekazanie HomeSnapshot do @report,
  9. uruchomienie serwera i panelu z gotowego System Bundle bez checkoutu i toolchainu.
sequenceDiagram
    participant U as Użytkownik
    participant S1 as Serwer 1
    participant DB as SQLite
    participant S2 as Serwer 2
    participant P as Panel
    U->>S1: zapisz obserwację
    S1->>DB: commit
    S1--xS1: kontrolowane zakończenie
    S2->>DB: odczytaj po restarcie
    DB-->>S2: zatwierdzony rekord
    S2->>P: HomeSnapshot
    P-->>U: odtworzony stan

Granice pozostają jawne: nie ma jeszcze rejestru zdalnych bibliotek, automatycznego ORM-u, rozproszonych transakcji ani bezpośredniego dostępu @report do bazy.

Przejdź do przewodnika: moduły i baza danych, aby zobaczyć strukturę projektu, kod kontraktu, wybór providera, migracje, artefakty kontrolne oraz rytm pracy dewelopera.