235 lines
6.2 KiB
Markdown
235 lines
6.2 KiB
Markdown
# Enum-to-Class Principle: Single Source of Truth
|
|
|
|
**Version**: 1.0
|
|
**Last Updated**: 2025-12-06
|
|
**Status**: Active Rule
|
|
|
|
---
|
|
|
|
## Core Principle
|
|
|
|
**Enums are TEMPORARY scaffolding. Once an enum is promoted to a class hierarchy, the enum MUST be deleted to maintain a Single Source of Truth.**
|
|
|
|
---
|
|
|
|
## Rationale
|
|
|
|
### The Problem: Dual Representation
|
|
|
|
When both an enum AND a class hierarchy exist for the same concept:
|
|
- **Data sync issues**: Enum values and class names can drift apart
|
|
- **Maintenance burden**: Changes must be made in two places
|
|
- **Developer confusion**: Which one should I use?
|
|
- **Validation conflicts**: Enum constraints vs class ranges may diverge
|
|
|
|
### The Solution: Single Source of Truth
|
|
|
|
- **Enums**: Use for simple, fixed value constraints (e.g., `DataTierEnum: TIER_1, TIER_2, TIER_3, TIER_4`)
|
|
- **Classes**: Use when the concept needs properties, relationships, or rich documentation
|
|
- **NEVER BOTH**: Once promoted to classes, DELETE the enum
|
|
|
|
---
|
|
|
|
## When to Promote Enum to Classes
|
|
|
|
**Promote when the concept needs**:
|
|
|
|
| Need | Enum Can Do? | Class Required? |
|
|
|------|-------------|-----------------|
|
|
| Fixed value constraint | ✅ Yes | ✅ Yes |
|
|
| Properties (e.g., `role_category`, `typical_domains`) | ❌ No | ✅ Yes |
|
|
| Rich description per value | Limited | ✅ Yes |
|
|
| Relationships to other entities | ❌ No | ✅ Yes |
|
|
| Inheritance hierarchy | ❌ No | ✅ Yes |
|
|
| Independent identity (URI) | Limited | ✅ Yes |
|
|
| Ontology class mapping (`class_uri`) | Via `meaning` | ✅ Native |
|
|
|
|
**Rule of thumb**: If you're adding detailed documentation to each enum value, or want to attach properties, it's time to promote to classes.
|
|
|
|
---
|
|
|
|
## Promotion Workflow
|
|
|
|
### Step 1: Create Class Hierarchy
|
|
|
|
```yaml
|
|
# modules/classes/StaffRole.yaml (base class)
|
|
StaffRole:
|
|
abstract: true
|
|
description: Base class for staff role categories
|
|
slots:
|
|
- role_id
|
|
- role_name
|
|
- role_category
|
|
- typical_domains
|
|
|
|
# modules/classes/StaffRoles.yaml (subclasses)
|
|
Curator:
|
|
is_a: StaffRole
|
|
description: Museum curator specializing in collection research...
|
|
|
|
Conservator:
|
|
is_a: StaffRole
|
|
description: Conservator specializing in preservation...
|
|
```
|
|
|
|
### Step 2: Update Slot Ranges
|
|
|
|
```yaml
|
|
# BEFORE (enum)
|
|
staff_role:
|
|
range: StaffRoleTypeEnum
|
|
|
|
# AFTER (class)
|
|
staff_role:
|
|
range: StaffRole
|
|
```
|
|
|
|
### Step 3: Update Modular Schema Imports
|
|
|
|
```yaml
|
|
# REMOVE enum import
|
|
# - modules/enums/StaffRoleTypeEnum # DELETED
|
|
|
|
# ADD class imports
|
|
- modules/classes/StaffRole
|
|
- modules/classes/StaffRoles
|
|
```
|
|
|
|
### Step 4: Archive the Enum
|
|
|
|
```bash
|
|
mkdir -p schemas/.../archive/enums
|
|
mv modules/enums/OldEnum.yaml archive/enums/OldEnum.yaml.archived_$(date +%Y%m%d)
|
|
```
|
|
|
|
### Step 5: Document the Change
|
|
|
|
- Update `archive/enums/README.md` with migration entry
|
|
- Add comment in modular schema explaining removal
|
|
- Update any documentation referencing the old enum
|
|
|
|
---
|
|
|
|
## Example: StaffRoleTypeEnum → StaffRole
|
|
|
|
**Before** (2025-12-05):
|
|
```yaml
|
|
# StaffRoleTypeEnum.yaml
|
|
StaffRoleTypeEnum:
|
|
permissible_values:
|
|
CURATOR:
|
|
description: Museum curator
|
|
CONSERVATOR:
|
|
description: Conservator
|
|
# ... 51 values with limited documentation
|
|
```
|
|
|
|
**After** (2025-12-06):
|
|
```yaml
|
|
# StaffRole.yaml (abstract base)
|
|
StaffRole:
|
|
abstract: true
|
|
slots:
|
|
- role_id
|
|
- role_name
|
|
- role_category
|
|
- typical_domains
|
|
- typical_responsibilities
|
|
- requires_qualification
|
|
|
|
# StaffRoles.yaml (51 subclasses)
|
|
Curator:
|
|
is_a: StaffRole
|
|
class_uri: schema:curator
|
|
description: |
|
|
Museum curator specializing in collection research...
|
|
|
|
**IMPORTANT - FORMAL TITLE vs DE FACTO WORK**:
|
|
This is the OFFICIAL job appellation/title. Actual work may differ.
|
|
slot_usage:
|
|
role_category:
|
|
equals_string: CURATORIAL
|
|
typical_domains:
|
|
equals_expression: "[Museums, Galleries]"
|
|
```
|
|
|
|
**Why the promotion?**
|
|
1. Need to distinguish FORMAL TITLE from DE FACTO WORK
|
|
2. Each role has `role_category`, `common_variants`, `typical_domains`, `typical_responsibilities`
|
|
3. Roles benefit from inheritance (`Curator is_a StaffRole`)
|
|
4. Richer documentation per role
|
|
|
|
---
|
|
|
|
## Enums That Should REMAIN Enums
|
|
|
|
Some enums are appropriate as permanent fixtures:
|
|
|
|
| Enum | Why Keep as Enum |
|
|
|------|------------------|
|
|
| `DataTierEnum` | Simple 4-value tier (TIER_1 through TIER_4), no properties needed |
|
|
| `DataSourceEnum` | Fixed source types, simple strings |
|
|
| `CountryCodeEnum` | ISO 3166-1 standard, no custom properties |
|
|
| `LanguageCodeEnum` | ISO 639 standard, no custom properties |
|
|
|
|
**Characteristics of "permanent" enums**:
|
|
- Based on external standards (ISO, etc.)
|
|
- Simple values with no need for properties
|
|
- Unlikely to require rich per-value documentation
|
|
- Used purely for validation/constraint
|
|
|
|
---
|
|
|
|
## Anti-Patterns
|
|
|
|
### ❌ WRONG: Keep Both Enum and Classes
|
|
|
|
```yaml
|
|
# modules/enums/StaffRoleTypeEnum.yaml # ← Still exists!
|
|
# modules/classes/StaffRole.yaml # ← Also exists!
|
|
# Which one is authoritative? CONFUSION!
|
|
```
|
|
|
|
### ❌ WRONG: Create Classes but Keep Enum "for backwards compatibility"
|
|
|
|
```yaml
|
|
# "Let's keep the enum for old code"
|
|
# Result: Two sources of truth, guaranteed drift
|
|
```
|
|
|
|
### ✅ CORRECT: Delete Enum After Creating Classes
|
|
|
|
```yaml
|
|
# modules/enums/StaffRoleTypeEnum.yaml # ← ARCHIVED
|
|
# modules/classes/StaffRole.yaml # ← Single source of truth
|
|
# modules/classes/StaffRoles.yaml # ← All 51 role subclasses
|
|
```
|
|
|
|
---
|
|
|
|
## Verification Checklist
|
|
|
|
After promoting an enum to classes:
|
|
|
|
- [ ] Old enum file moved to `archive/enums/`
|
|
- [ ] Modular schema import removed for enum
|
|
- [ ] Modular schema import added for new class(es)
|
|
- [ ] All slot ranges updated from enum to class
|
|
- [ ] No grep results for old enum name in active schema files
|
|
- [ ] `archive/enums/README.md` updated with migration entry
|
|
- [ ] Comment added in modular schema explaining removal
|
|
|
|
```bash
|
|
# Verify enum is fully removed (should return only archive hits)
|
|
grep -r "StaffRoleTypeEnum" schemas/20251121/linkml/
|
|
```
|
|
|
|
---
|
|
|
|
## See Also
|
|
|
|
- `docs/ENUM_CLASS_SINGLE_SOURCE.md` - Extended documentation
|
|
- `schemas/20251121/linkml/archive/enums/README.md` - Archive directory
|
|
- LinkML documentation on enums: https://linkml.io/linkml/schemas/enums.html
|
|
- LinkML documentation on classes: https://linkml.io/linkml/schemas/models.html
|