Skip to content

Commit 73acdce

Browse files
committed
docs: add file creation decision matrix for global features and module implementation
1 parent 19a6568 commit 73acdce

File tree

1 file changed

+204
-0
lines changed

1 file changed

+204
-0
lines changed

docs/vue-project-modules-blueprint.md

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2044,3 +2044,207 @@ shared/ (Purple Background)
20442044
- [ ] Document team guidelines and examples
20452045
20462046
**Result**: A visually clear, maintainable modular architecture that scales with your team and project complexity.
2047+
2048+
## File Creation Decision Matrix for Global Features
2049+
2050+
When implementing new global features, follow this decision matrix to ensure consistent architecture:
2051+
2052+
| File Type | When to Create | Responsibilities | What Should NOT Go Inside |
2053+
| --------------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
2054+
| **`src/entities/<Feature>.ts`** | **Always** | • Zod schema definition; • Type exports; • Field-level validation rules; • Schema-derived utilities | • RPC calls; • Data transformation logic; • Business rules; • Vue composables |
2055+
| **`src/services/<feature>.ts`** | **Always** | • RPC/API calls; • Response → domain mapping; • Validation using entity schema; • Caching (export `cached` ref); • Error handling | • Vue composables; • UI-specific logic; • Component imports |
2056+
| **`src/services/<feature>.helpers.ts`** | **When business logic is complex** | • Pure transformation functions; • Business rules (if/then logic); • Data normalization; • Cross-field validations | • Vue reactivity; • RPC calls; • Side effects |
2057+
| **`src/composables/use<Feature>.ts`** | **Always** | • Reactive wrapper around service; • `load()` method calling service; • UI-friendly computed refs; • Vue lifecycle hooks | • RPC calls; • Data transformation; • Business logic |
2058+
| **`src/stores/use<Feature>Store.ts`** | **When you need cross-component state** | • Pinia store actions; • UI state management; • Service orchestration; • Loading/error states | • RPC calls (delegate to service); • Business logic (delegate to service) |
2059+
2060+
### Layer Import Rules
2061+
2062+
To maintain clean architecture, follow these import restrictions:
2063+
2064+
- ✅ **Services** can import: `entities`, `helpers`
2065+
- ✅ **Composables** can import: `services` (cached refs only)
2066+
- ✅ **Stores** can import: `services`
2067+
- ❌ **Services** cannot import: `composables`, `stores`
2068+
- ❌ **Entities** cannot import: anything except other entities
2069+
2070+
### Quick Decision Flow
2071+
2072+
For any new feature, ask:
2073+
2074+
1. **"Do I need a data contract?"** → Create entity
2075+
2. **"Do I need to fetch/process data?"** → Create service
2076+
3. **"Is the processing logic complex?"** → Create service helpers
2077+
4. **"Do I need reactive UI state?"** → Create composable
2078+
5. **"Do I need cross-component state management?"** → Create store
2079+
2080+
**Always create**: Entity + Service + Composable
2081+
**Sometimes create**: Helpers + Store
2082+
2083+
### Example Implementation
2084+
2085+
```typescript
2086+
// src/entities/SystemDetails.ts - Data contract only
2087+
export const SystemDetailsSchema = z.object({
2088+
'product-name': z.string(),
2089+
'hw-version': z.string(),
2090+
})
2091+
export type SystemDetails = z.infer<typeof SystemDetailsSchema>
2092+
2093+
// src/services/systemDetails.ts - I/O + orchestration
2094+
export const cachedDetails = ref<SystemDetails | null>(null)
2095+
export const systemDetailsService = {
2096+
async fetchDetails(th: string | number): Promise<SystemDetails> {
2097+
// RPC call, validation, caching
2098+
},
2099+
}
2100+
2101+
// src/composables/useSystemDetails.ts - UI wrapper
2102+
export const useSystemDetails = createGlobalState(() => {
2103+
const systemDetails = computed(() => cachedDetails.value)
2104+
const loadValues = async () => {
2105+
await systemDetailsService.fetchDetails(session.value.th)
2106+
}
2107+
return { systemDetails, loadValues }
2108+
})
2109+
```
2110+
2111+
## File Creation Decision Matrix for Module/Feature Implementation
2112+
2113+
When implementing features within specific modules (under `src/modules/<module-name>/`), follow this decision matrix:
2114+
2115+
| File Type | When to Create | Responsibilities | What Should NOT Go Inside |
2116+
| --------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
2117+
| **`types.ts`** | **Always** | • Zod schemas for module data; • TypeScript type definitions; • Module-specific validation rules; • Type exports for public API | • Business logic; • API calls; • Vue composables; • Component logic |
2118+
| **`services/<feature>.ts`** | **Always** | • Module-specific API calls; • RPC/HTTP requests; • Response transformation; • Error handling; • Request/response mapping | • Vue reactivity; • UI logic; • Component imports; • Global state management |
2119+
| **`services/helpers.ts`** | **When API logic is complex** | • Pure transformation functions; • Request/response mapping; • Data normalization; • Validation helpers | • Vue composables; • Side effects; • Global state; • UI concerns |
2120+
| **`composables/use<Feature>.ts`** | **When UI needs reactive state** | • Module-specific reactive logic; • Local state management; • UI lifecycle hooks; • Form handling | • Direct API calls (use services/ instead); • Global state; • Cross-module dependencies |
2121+
| **`components/<Component>.vue`** | **For reusable components** | • Presentation logic; • User interactions; • Local component state; • Props/events handling | • API calls; • Business logic; • Cross-module imports; • Global state mutations |
2122+
| **`views/<View>.vue`** | **For page-level views** | • Page layout and structure; • Route handling; • Composition of components; • Page-specific state | • Direct API calls; • Business logic; • Reusable component logic |
2123+
| **`index.ts`** | **Always** | • Module public API exports; • External interface definition; • Re-exports of public components/composables | • Implementation details; • Internal utilities; • Private components |
2124+
2125+
### Module Layer Import Rules
2126+
2127+
Within modules, follow these import restrictions:
2128+
2129+
- ✅ **`services/`** can import: `types.ts`, `services/helpers.ts`, global `@/entities`
2130+
- ✅ **`composables/`** can import: `services/`, `types.ts`
2131+
- ✅ **`components/`** can import: `composables/`, `types.ts`, global `@/shared/components`
2132+
- ✅ **`views/`** can import: `components/`, `composables/`, `types.ts`
2133+
- ✅ **`index.ts`** can import: any file within the module (for re-export)
2134+
- ❌ **`services/`** cannot import: `composables/`, `components/`, `views/`
2135+
- ❌ **`types.ts`** cannot import: anything except other types and global entities
2136+
- ❌ **Modules** cannot import: internal files from other modules (only via `index.ts`)
2137+
2138+
### Module Decision Flow
2139+
2140+
For any new module feature, ask:
2141+
2142+
1. **"Do I need module-specific types?"** → Create/update `types.ts`
2143+
2. **"Do I need to fetch external data?"** → Create `services/<feature>.ts`
2144+
3. **"Is the API logic complex?"** → Create `services/helpers.ts`
2145+
4. **"Do I need reactive UI state?"** → Create `composables/use<Feature>.ts`
2146+
5. **"Do I need reusable components?"** → Create `components/<Component>.vue`
2147+
6. **"Do I need page views?"** → Create `views/<View>.vue`
2148+
7. **"Should this be available to other modules?"** → Export via `index.ts`
2149+
2150+
### Module Structure Example
2151+
2152+
```plaintext
2153+
src/modules/port-configuration/
2154+
├── types.ts # Port-specific schemas and types
2155+
├── services/
2156+
│ ├── ports.ts # Port CRUD operations
2157+
│ ├── configurations.ts # Configuration API calls
2158+
│ └── helpers.ts # API transformation utilities
2159+
├── composables/
2160+
│ ├── usePortEditor.ts # Port editing state
2161+
│ └── usePortValidation.ts # Port validation logic
2162+
├── components/
2163+
│ ├── PortList.vue # Port listing component
2164+
│ ├── PortEditor.vue # Port editing form
2165+
│ └── PortStatus.vue # Port status display
2166+
├── views/
2167+
│ ├── PortConfigurationView.vue # Main port configuration page
2168+
│ └── PortDetailsView.vue # Individual port details page
2169+
└── index.ts # Public API exports
2170+
```
2171+
2172+
### Example Module Implementation
2173+
2174+
```typescript
2175+
// src/modules/port-configuration/types.ts
2176+
export const PortSchema = z.object({
2177+
id: z.string(),
2178+
name: z.string(),
2179+
status: z.enum(['active', 'inactive']),
2180+
})
2181+
export type Port = z.infer<typeof PortSchema>
2182+
2183+
// src/modules/port-configuration/services/ports.ts
2184+
import { PortSchema, type Port } from '../types'
2185+
2186+
export const portsService = {
2187+
async fetchPorts(): Promise<Port[]> {
2188+
const response = await fetchJsonRpc({ method: 'get_ports' })
2189+
return response.map((port) => PortSchema.parse(port))
2190+
},
2191+
2192+
async updatePort(port: Port): Promise<void> {
2193+
await fetchJsonRpc({ method: 'update_port', params: port })
2194+
},
2195+
}
2196+
2197+
// src/modules/port-configuration/composables/usePortEditor.ts
2198+
import { ref } from 'vue'
2199+
import { portsService } from '../services/ports'
2200+
import type { Port } from '../types'
2201+
2202+
export function usePortEditor() {
2203+
const selectedPort = ref<Port | null>(null)
2204+
const loading = ref(false)
2205+
2206+
const loadPort = async (id: string) => {
2207+
loading.value = true
2208+
try {
2209+
const ports = await portsService.fetchPorts()
2210+
selectedPort.value = ports.find((p) => p.id === id) || null
2211+
} finally {
2212+
loading.value = false
2213+
}
2214+
}
2215+
2216+
return { selectedPort, loading, loadPort }
2217+
}
2218+
2219+
// src/modules/port-configuration/index.ts - Public API
2220+
export type { Port } from './types'
2221+
export { usePortEditor } from './composables/usePortEditor'
2222+
export { default as PortList } from './components/PortList.vue'
2223+
export { default as PortEditor } from './components/PortEditor.vue'
2224+
export { default as PortConfigurationView } from './views/PortConfigurationView.vue'
2225+
```
2226+
2227+
### Cross-Module Communication
2228+
2229+
When modules need to communicate:
2230+
2231+
```typescript
2232+
// ✅ CORRECT - Import via public API
2233+
import { usePortEditor, type Port } from '@/modules/port-configuration'
2234+
2235+
// ❌ WRONG - Direct import of internal files
2236+
import { usePortEditor } from '@/modules/port-configuration/composables/usePortEditor'
2237+
```
2238+
2239+
### Module vs Global Decision
2240+
2241+
| Scope | Use Module Files | Use Global Files |
2242+
| --------------------------- | ----------------------------------------------------------- | --------------------------------- |
2243+
| **Feature-specific** | Module `services/`, `composables/`, `components/`, `views/` | Never |
2244+
| **Reusable across modules** | Export via `index.ts` then decide | Often better as global |
2245+
| **Shared types/entities** | If module-specific | Global `src/entities/` |
2246+
| **Common services** | If module-specific | Global `src/services/` |
2247+
| **UI components** | If module-specific | Global `src/shared/components/` |
2248+
| **Page views** | Always module-specific | Never (views are module-specific) |
2249+
2250+
This structure ensures modules are self-contained while maintaining clean interfaces for cross-module communication.

0 commit comments

Comments
 (0)