kell_creations/kell_creations_apps/docs/composition-strategy.md

205 lines
8.3 KiB
Markdown

# Kell Creations — Cross-Platform Shell Composition Strategy
## Overview
This document describes the shared composition pattern that all Kell Creations
app targets (`kell_web`, `kell_mobile`, future targets) must follow. The pattern
lives in the `core` package and provides a consistent way to:
1. Read runtime configuration from `--dart-define` values.
2. Construct environment-appropriate services (fake vs. real backends).
3. Expose services and configuration to the widget tree.
## Architecture
```
┌──────────────────────────────────────────────────────────────┐
│ core (shared package) │
│ │
│ KcAppConfig / KcAppEnvironment │
│ └─ Runtime config from --dart-define │
│ │
│ KcAppServices (abstract) │
│ └─ Base class for app service containers │
│ │
│ KcServiceFactory<T extends KcAppServices> │
│ └─ Generic factory: createFake() / createWordPress(cfg) │
│ │
│ KcBootstrap │
│ └─ Shared bootstrap: env switch + WP credential fallback │
│ │
│ KcAppScope<T extends KcAppServices> │
│ └─ InheritedWidget exposing T + KcAppConfig to tree │
└──────────────────────────────────────────────────────────────┘
▲ ▲
│ │
┌────────┴───────────┐ ┌────────────┴──────────────┐
│ kell_web (app) │ │ kell_mobile (app) │
│ │ │ │
│ AppServices │ │ MobileAppServices │
│ extends │ │ extends │
│ KcAppServices │ │ KcAppServices │
│ │ │ │
│ AppScope │ │ (uses KcAppScope │
│ extends │ │ <MobileAppServices>) │
│ KcAppScope │ │ │
│ <AppServices> │ │ │
│ │ │ │
│ Bootstrap │ │ (uses KcBootstrap.run │
│ delegates to │ │ with own factory) │
│ KcBootstrap.run │ │ │
└─────────────────────┘ └────────────────────────────┘
```
## Why abstract services?
Concrete service containers hold references to feature-package repository types
(e.g., `InventoryRepository`, `ProductPublishingRepository`). Moving those
references into `core` would create **circular dependencies**:
```
core → feature_wordpress → core ← NOT ALLOWED
```
By keeping `KcAppServices` abstract in `core`, the package owns the composition
**contract** without knowing concrete types. Each app provides its own concrete
subclass.
## Contract for new app targets
When creating a new app target (e.g., `kell_mobile`), follow this pattern:
### 1. Create a concrete services class
```dart
// apps/kell_mobile/lib/composition/mobile_app_services.dart
import 'package:core/core.dart';
import 'package:feature_inventory/feature_inventory.dart';
import 'package:feature_wordpress/feature_wordpress.dart';
// ... other feature imports
class MobileAppServices extends KcAppServices {
final InventoryRepository inventoryRepository;
final ProductPublishingRepository productPublishingRepository;
// ... other repositories as needed
const MobileAppServices({
required this.inventoryRepository,
required this.productPublishingRepository,
});
factory MobileAppServices.fake() {
return MobileAppServices(
inventoryRepository: FakeInventoryRepository(),
productPublishingRepository: FakeProductPublishingRepository(),
);
}
factory MobileAppServices.wordpress({
required String siteUrl,
required String consumerKey,
required String consumerSecret,
}) {
final apiClient = WooCommerceApiClient(
siteUrl: siteUrl,
consumerKey: consumerKey,
consumerSecret: consumerSecret,
);
return MobileAppServices(
inventoryRepository: FakeInventoryRepository(),
productPublishingRepository:
WordPressProductPublishingRepository(apiClient: apiClient),
);
}
static KcServiceFactory<MobileAppServices> get serviceFactory {
return KcServiceFactory<MobileAppServices>(
createFake: () => MobileAppServices.fake(),
createWordPress: (config) => MobileAppServices.wordpress(
siteUrl: config.wcSiteUrl,
consumerKey: config.wcConsumerKey,
consumerSecret: config.wcConsumerSecret,
),
);
}
}
```
### 2. Wire up main.dart
```dart
// apps/kell_mobile/lib/main.dart
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'composition/mobile_app_services.dart';
void main() {
final config = KcAppConfig.fromEnvironment();
final (:services, config: effectiveConfig) = KcBootstrap.run(
config,
MobileAppServices.serviceFactory,
);
runApp(
KcAppScope<MobileAppServices>(
services: services,
config: effectiveConfig,
child: const KellMobileApp(),
),
);
}
```
### 3. Access services in widgets
```dart
// Anywhere in the widget tree:
final services = KcAppScope.of<MobileAppServices>(context);
final config = KcAppScope.configOf<MobileAppServices>(context);
```
## Backward compatibility (kell_web)
The `kell_web` app retains its original class names (`AppConfig`, `AppScope`,
`Bootstrap`, `AppServices`) as thin wrappers/typedefs around the shared `core`
types. Existing code continues to work without modification:
| Original (kell_web) | Shared (core) | Relationship |
| ------------------- | ------------------ | --------------- |
| `AppConfig` | `KcAppConfig` | typedef |
| `AppEnvironment` | `KcAppEnvironment` | typedef |
| `AppServices` | `KcAppServices` | extends |
| `AppScope` | `KcAppScope` | extends (typed) |
| `Bootstrap` | `KcBootstrap` | delegates |
New code in `kell_web` may use either the original names or the shared `Kc`-prefixed
names. The shared names are preferred for clarity and consistency with `kell_mobile`.
## Runtime configuration
All app targets use the same `--dart-define` keys:
| Key | Description | Default |
| ----------------------- | ------------------------------------ | ------- |
| `KC_ENV` | `fake` or `wordpress` | `fake` |
| `KC_WC_SITE_URL` | WordPress site URL | (empty) |
| `KC_WC_CONSUMER_KEY` | WooCommerce REST API consumer key | (empty) |
| `KC_WC_CONSUMER_SECRET` | WooCommerce REST API consumer secret | (empty) |
When `KC_ENV=wordpress` but credentials are missing, `KcBootstrap` automatically
falls back to fake mode with a debug-mode warning.
## What remains app-specific
The following concerns are **not** shared and remain in each app target:
- **App shell / navigation** — Web uses `NavigationRail`, mobile will use
`BottomNavigationBar` or similar.
- **Routing** — Route definitions and page builders are platform-specific.
- **Platform-specific presentation** — Layout, responsive breakpoints, etc.
- **Cross-feature navigation handoffs** — Wired in the app's routing layer.
The `design_system` package provides shared visual components (widgets, theme,
typography, breakpoints) that both web and mobile can use for consistent styling.