# Application Architecture This chapter describes how to structure applications built with DSM and Viper. The architecture separates concerns into layers, enabling code reuse across platforms and languages. ## The 3-Tier Architecture Applications built with Viper follow a 3-tier pattern: ```text ┌────────────────────────────────────────────────────────────┐ │ TIER 1: PRESENTATION (Platform-Specific UI) │ │ AppKit (macOS) / Qt (cross-platform) / PySide (Python) │ └────────────────────────────┬───────────────────────────────┘ │ ┌────────────────────────────▼───────────────────────────────┐ │ TIER 2: LOGIC (Context + Bridges + Components) │ │ Application Context (orchestration), generated pools, │ │ hand-written business code │ └────────────────────────────┬───────────────────────────────┘ │ ┌────────────────────────────▼───────────────────────────────┐ │ TIER 3: DATA (Viper Runtime) │ │ CommitDatabase, CommitStore, persistence, dsviper binding │ └────────────────────────────────────────────────────────────┘ ``` ### Tier Responsibilities | Tier | Responsibility | Platform-Specific? | |------------------|---------------------------------------------------------------|--------------------| | **Presentation** | UI framework, windows, dialogs | Yes | | **Logic** | Application Context, bridges, business components | Partially | | **Data** | Viper runtime (CommitDatabase, CommitStore), persistence | No (shared) | ## The Full Application Stack For business applications with domain logic, the stack expands: ```text ┌────────────────────────────────────────────────────────────┐ │ 1. UI Layer │ │ Platform-specific windows, views, controllers │ └────────────────────────────┬───────────────────────────────┘ │ ┌────────────────────────────▼───────────────────────────────┐ │ 2. Application Context │ │ Singleton orchestrator: owns the CommitStore, the │ │ generated FunctionPools, and the application's domain │ │ state (e.g. current GraphKey) │ └────────────────────────────┬───────────────────────────────┘ │ ┌────────────────────────────▼───────────────────────────────┐ │ 3. Bridge Layer │ │ Pool signatures → Business logic connection │ └────────────────────────────┬───────────────────────────────┘ │ ┌────────────────────────────▼───────────────────────────────┐ │ 4. Business Logic Layer │ │ Hand-written domain code (Model_*) │ └────────────────────────────┬───────────────────────────────┘ │ ┌────────────────────────────▼───────────────────────────────┐ │ 5. Generated Infrastructure │ │ Kibo output: Data, Commit, Serialization │ └────────────────────────────┬───────────────────────────────┘ │ ┌────────────────────────────▼───────────────────────────────┐ │ 6. Viper Runtime │ │ Core C++ engine (CommitStore, CommitDatabase), │ │ dsviper Python binding │ └────────────────────────────────────────────────────────────┘ ``` ## Layer Details ### Layer 1: UI Platform-specific presentation code: - Window management - Menu and toolbar actions - Dialog boxes - Framework-native widgets This layer changes for each target platform but follows the same logical structure. ### Layer 2: Application Context The **Context** is the application-level singleton that orchestrates the runtime. It is *not* a subclass of `Viper::CommitStore`: it **owns** a `CommitStore` (provided by the Viper runtime) and adds the application-specific surface around it — the generated `FunctionPool` / `AttachmentFunctionPool` instances, the domain state (e.g. the current graph), and the action dispatch. Responsibilities of the Context: - Own the `CommitStore` (which itself holds the `CommitDatabase` and the current `CommitState`) - Hold the generated tool / model / attachment `FunctionPools` - Hold application-level domain state (e.g. `graphKey`) - Open / close / load the database - Dispatch user actions through `CommitMutableState` - Expose framework-agnostic notifications via `CommitStoreNotifying` ```cpp // Context pattern (C++) — see com.digitalsubstrate.ge / GE_Context.hpp namespace GE { class Context final { public: static std::shared_ptr Instance(); // Database lifecycle (owned via the store) void use(std::shared_ptr const & database); void close(); void load(); static std::shared_ptr createDatabase( std::filesystem::path const & filePath); // Domain operations void newGraph(); void useNewGraph(std::string const & label); void dispatchAction(std::string const & label, std::shared_ptr const & action); // Owned components std::shared_ptr store; std::shared_ptr tools; std::shared_ptr modelGraph; std::shared_ptr modelSelection; std::shared_ptr modelIntegrity; std::shared_ptr attachments; // Domain state GE::Graph::GraphKey graphKey; }; } // namespace GE ``` Action dispatch always goes through the store's `CommitMutableState`, so undo/redo and notifications stay coherent: ```cpp void Context::dispatchAction(std::string const & label, std::shared_ptr const & action) { auto const ms{store->mutableState()}; action->run(ms); store->commitMutations(label, ms); } ``` The previous "Store-as-application-singleton" pattern is superseded: the generic store concerns (database, state, undo/redo, notifications) live in `Viper::CommitStore`, and the application-specific concerns (which pools, which domain state, which actions) live in the application's `Context`. ### Layer 3: Bridges Bridges connect generated pool signatures to your business logic: ```cpp // GE_AttachmentFunctionPoolBridges.hpp (GENERATED) namespace GE::AttachmentFunctionPoolBridges { namespace ModelGraph { Graph::VertexKey new_vertex(std::shared_ptr const & attachmentMutating, Graph::GraphKey const & graphKey, std::int64_t value, Graph::Position const & position); } } // GE_AttachmentFunctionPoolBridges.cpp (HAND-WRITTEN) Graph::VertexKey new_vertex(std::shared_ptr const & attachmentMutating, Graph::GraphKey const & graphKey, std::int64_t value, Graph::Position const & position) { return Model::Vertex::add(attachmentMutating, graphKey, value, position, Model::Random::makeColor()); } ``` ### Layer 4: Business Logic Your domain-specific code: ```cpp // GE_Model_Vertex.cpp VertexKey add(std::shared_ptr const & attachmentMutating, GraphKey const & graphKey, std::int64_t const & value, Position const & position, Color const & color) { auto const vertexKey{create(attachmentMutating, value, position, color)}; Attachments::Graph_Topology::unionVertexKeys(attachmentMutating, graphKey, {vertexKey}); return vertexKey; } ``` ### Layer 5: Generated Infrastructure Kibo generates this layer from your DSM: - Data types (`*_Data.cpp`) - Commit accessors (`*_Commit.cpp`) - Serialization (`*_Reader.cpp`, `*_Writer.cpp`) - Database persistence (`*_Database.cpp`) - Python proxy ### Layer 6: Viper Runtime The core C++ engine providing: - Type and value system - CommitDatabase persistence - Stateful commits - Python binding (dsviper) ## The Notifier Pattern UI frameworks have different notification mechanisms. The **Notifier** pattern bridges this gap: ```text CommitStore (C++ core) │ ▼ CommitStoreNotifying (interface) │ ▼ DSCommitStoreNotifier (framework adapter) │ ▼ Framework-specific notifications ``` ### Common Notifications All implementations expose the same signals: | Signal | Purpose | |--------------------------|-------------------------------------| | `database_did_open` | Database opened successfully | | `database_did_close` | Database closed | | `state_did_change` | State changed (undo/redo available) | | `definitions_did_change` | Schema updated | | `dispatch_error` | Error occurred | ## Multi-Language Support The architecture supports multiple languages: | Layer | C++ Application | Python Application | |----------------|----------------------------------|--------------------------| | UI | AppKit/Qt C++ | PySide/Qt Python | | Context | C++ singleton (owns CommitStore) | Python class (owns store)| | Business | C++ | Python or dsviper | | Infrastructure | C++ generated | Python generated | | Runtime | Viper C++ (CommitStore, ...) | dsviper | ### Python Integration The `dsviper` module exposes the entire Viper runtime, including `CommitStore` and `CommitDatabase`. A Python application's `Context` follows the same pattern as the C++ one — it instantiates a `CommitStore` and wires the generated pools around it: ```python import dsviper database = dsviper.CommitDatabase.create("path/to/data.cdb", "An Application Database") store = dsviper.CommitStore.make() store.set_database(database) # Undo/Redo operations if store.can_undo(): store.undo() ``` ## Design Principles ### Separation of Concerns Each layer has a single responsibility: - **UI**: Only presentation, no business logic - **Context**: Orchestration only — owns the `CommitStore` and the pools, does not reimplement them - **Bridges**: Connection, not logic - **Business**: Domain rules, not infrastructure - **Generated**: Infrastructure, not business ### Platform Isolation Platform-specific code lives only in Layer 1. The other layers are shared or generated. ### Generated vs Hand-Written | Generated (Kibo) | Hand-Written | |------------------|---------------------| | Data types | Business logic | | Serialization | Bridges | | Persistence | Store orchestration | | Pool marshalling | UI layer | ### Minimal Porting Effort To port to a new platform: 1. Implement the Notifier adapter (~50 lines) 2. Create UI components matching existing patterns 3. Wire up the main window The business logic and infrastructure remain unchanged. ## Summary | Principle | Implementation | |------------------------|-----------------------------------------------| | **Separation** | 6 distinct layers with clear responsibilities | | **Reuse** | Layers 3-6 shared across platforms | | **Generation** | Kibo generates infrastructure (Layer 5) | | **Multi-language** | Same architecture for C++ and Python | | **Platform isolation** | Only Layer 1 is platform-specific | This architecture enables building complex applications that work across platforms while minimizing duplicated code. ## What's Next - [Python Guide](../python/index.rst) - Use the dsviper Python API - [Toolchain](../tools/index.rst) - Master the development tools