Skip to content

Commit b3f4c12

Browse files
Merge pull request #17 from saltines321-debug/issue-13-modular-reference-example
Add modular architecture reference example
2 parents b06c677 + 5ae83b5 commit b3f4c12

3 files changed

Lines changed: 147 additions & 1 deletion

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ This template is intentionally generic so it can be adopted across teams, produc
3232
- `docs/REPOSITORY_STANDARDS.md`
3333
- `docs/ARCHITECTURE.md`
3434
- `docs/DEVEX.md`
35+
- `docs/examples/MODULAR_REFERENCE.md`
3536
- Modular template structure:
3637
- `core/`
3738
- `providers/`
@@ -75,7 +76,7 @@ See `docs/REPOSITORY_STANDARDS.md` for reusable naming and governance convention
7576

7677
## Architecture conventions
7778

78-
See `docs/ARCHITECTURE.md` for the reusable modular layout, including core/provider/plugin/config/test separation.
79+
See `docs/ARCHITECTURE.md` for the reusable modular layout, including core/provider/plugin/config/test separation. For a concrete walkthrough, see `docs/examples/MODULAR_REFERENCE.md`.
7980

8081
## Developer experience
8182

@@ -107,6 +108,8 @@ docs/
107108
ARCHITECTURE.md
108109
DEVEX.md
109110
REPOSITORY_STANDARDS.md
111+
examples/
112+
MODULAR_REFERENCE.md
110113
scripts/
111114
tests/
112115
CHANGELOG.md

docs/ARCHITECTURE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ This repository is a reusable template, so the architecture intentionally define
44

55
Use these folders as stable seams for future projects. Keep implementation code small, composable, and easy to replace.
66

7+
For a tiny dependency-free walkthrough of the intended `core -> providers -> plugins` pattern, see `docs/examples/MODULAR_REFERENCE.md`.
8+
79
## Layout
810

911
```text

docs/examples/MODULAR_REFERENCE.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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

Comments
 (0)