|
| 1 | +# Modular Reference Example |
| 2 | + |
| 3 | +This example shows how to use the template's architecture without assuming a specific product, framework, or runtime. |
| 4 | + |
| 5 | +The goal is not to provide production code. The goal is to show the dependency direction: |
| 6 | + |
| 7 | +```text |
| 8 | +plugins -> providers -> core |
| 9 | +``` |
| 10 | + |
| 11 | +`core/` owns stable behavior. `providers/` adapt outside systems. `plugins/` compose optional capabilities. |
| 12 | + |
| 13 | +## Example scenario |
| 14 | + |
| 15 | +Imagine a project needs to normalize an input item, save it somewhere, and optionally expose that behavior as an extension. |
| 16 | + |
| 17 | +This can be modeled without committing to a database, API, queue, CLI, or web framework. |
| 18 | + |
| 19 | +## 1) Core behavior |
| 20 | + |
| 21 | +`core/` defines the stable rules and contracts. |
| 22 | + |
| 23 | +```text |
| 24 | +core/item-normalizer.pseudo |
| 25 | +
|
| 26 | +function normalizeItem(input): NormalizedItem |
| 27 | + require input.name is not empty |
| 28 | +
|
| 29 | + return { |
| 30 | + name: trim(input.name), |
| 31 | + slug: lowercase(replaceSpaces(input.name, "-")) |
| 32 | + } |
| 33 | +``` |
| 34 | + |
| 35 | +Why it belongs in `core/`: |
| 36 | + |
| 37 | +- it does not know where data came from |
| 38 | +- it does not know where data will be stored |
| 39 | +- it is easy to test in isolation |
| 40 | +- it can be reused by many interfaces |
| 41 | + |
| 42 | +## 2) Provider adapter |
| 43 | + |
| 44 | +`providers/` connects the core behavior to an outside system. |
| 45 | + |
| 46 | +```text |
| 47 | +providers/item-store.pseudo |
| 48 | +
|
| 49 | +interface ItemStore |
| 50 | + save(item): SaveResult |
| 51 | +
|
| 52 | +class FileItemStore implements ItemStore |
| 53 | + save(item) |
| 54 | + write item to configured file path |
| 55 | +``` |
| 56 | + |
| 57 | +Why it belongs in `providers/`: |
| 58 | + |
| 59 | +- file storage is an implementation detail |
| 60 | +- it can later be swapped for an API, database, queue, or memory store |
| 61 | +- `core/` does not need to change when storage changes |
| 62 | + |
| 63 | +## 3) Optional plugin |
| 64 | + |
| 65 | +`plugins/` wires optional behavior together. |
| 66 | + |
| 67 | +```text |
| 68 | +plugins/save-normalized-item.pseudo |
| 69 | +
|
| 70 | +function createSaveNormalizedItemPlugin(itemStore) |
| 71 | + return function run(input) |
| 72 | + item = normalizeItem(input) |
| 73 | + return itemStore.save(item) |
| 74 | +``` |
| 75 | + |
| 76 | +Why it belongs in `plugins/`: |
| 77 | + |
| 78 | +- it composes core behavior with a provider |
| 79 | +- it can be enabled, replaced, or removed |
| 80 | +- it keeps optional workflow logic outside the core layer |
| 81 | + |
| 82 | +## 4) Configuration |
| 83 | + |
| 84 | +`config/` documents runtime choices without hardcoding machine-specific values. |
| 85 | + |
| 86 | +```text |
| 87 | +config/item-store.example |
| 88 | +
|
| 89 | +ITEM_STORE_KIND=file |
| 90 | +ITEM_STORE_PATH=./data/items.json |
| 91 | +``` |
| 92 | + |
| 93 | +Keep real secrets, local-only paths, and machine-specific values out of committed files. |
| 94 | + |
| 95 | +## 5) Tests |
| 96 | + |
| 97 | +`tests/` should start closest to the core behavior. |
| 98 | + |
| 99 | +```text |
| 100 | +tests/unit/item-normalizer.test.pseudo |
| 101 | +
|
| 102 | +input: { name: " Example Item " } |
| 103 | +output: { name: "Example Item", slug: "example-item" } |
| 104 | +``` |
| 105 | + |
| 106 | +Suggested test layers: |
| 107 | + |
| 108 | +- unit tests for `core/` behavior |
| 109 | +- integration tests for `providers/` |
| 110 | +- plugin tests for composition boundaries |
| 111 | + |
| 112 | +## Dependency rule |
| 113 | + |
| 114 | +The important rule is direction: |
| 115 | + |
| 116 | +```text |
| 117 | +core: knows only stable rules |
| 118 | +providers: know outside systems and implement contracts |
| 119 | +plugins: compose optional workflows |
| 120 | +``` |
| 121 | + |
| 122 | +Avoid this: |
| 123 | + |
| 124 | +```text |
| 125 | +core -> providers |
| 126 | +core -> plugins |
| 127 | +``` |
| 128 | + |
| 129 | +That direction makes the template harder to reuse and harder to test. |
| 130 | + |
| 131 | +## How to adapt this example |
| 132 | + |
| 133 | +When starting a real project: |
| 134 | + |
| 135 | +1. Put pure behavior and contracts in `core/`. |
| 136 | +2. Put external systems in `providers/`. |
| 137 | +3. Put optional feature wiring in `plugins/`. |
| 138 | +4. Put safe examples and schemas in `config/`. |
| 139 | +5. Put validation coverage in `tests/`. |
| 140 | + |
| 141 | +Keep the seams boring. Boring seams make future changes cheap. |
0 commit comments