github.com/gemaraproj/gemara@v0.23.0

docs/adrs/0006-unified-package-structure.md raw

  1---
  2layout: page
  3title: Unified Go SDK Package Structure
  4---
  5
  6- **ADR:** 0006
  7- **Proposal Author(s):** @jpower432
  8- **Status:** Accepted; Modified by [ADR 0007](./0007-isolate-concepts-from-code) (SDK isolation); ; Modified by [ADR-0009](./0009-sensitive-activities) (numbering for layers 3+)
  9
 10## Context
 11
 12The Gemara Go module initially organized code by the conceptual layers of the Gemara model:
 13- `layer1/` - Guidance documents (Layer 1)
 14- `layer2/` - Control catalogs (Layer 2)  
 15- `layer3/` - Policy documents (Layer 3)
 16- `layer4/` - Evaluation logs and results (Layer 4)
 17
 18This structure mirrored the conceptual model described in the README, where each layer builds upon lower layers.
 19
 20However, over time some issues emerged:
 21
 221. **Type Sharing**: Many types are shared across layers (e.g., `Metadata`, `Contact`, `Mapping`, `Date`). These were duplicated between packages.
 23
 242. **Cross-Layer Usage**: Higher layers frequently reference lower layers:
 25   - Layer 4 (Evaluation) references Layer 2 (Catalog) controls
 26   - Converters need types from multiple layers
 27   - Loaders share common logic
 28
 293. **Import Complexity**: Consumers needed to import multiple packages:
 30   ```go
 31   import (
 32       "github.com/ossf/gemara/layer1"
 33       "github.com/ossf/gemara/layer2"
 34       "github.com/ossf/gemara/layer4"
 35   )
 36   ```
 37
 38## Decision
 39
 40We consolidated all layer Go packages into a single unified package: `package gemara` at the module root.
 41
 42### New Structure
 43
 44All Go files are now in the root package:
 45- `generated_types.go` - All types from all layers (generated from CUE schemas)
 46- `loaders.go` - Unified loader functions for all document types
 47- `assessment_log.go` - Layer 4 evaluation functionality
 48- `control_evaluation.go` - Layer 4 control evaluation
 49- `evaluation_plan.go` - Layer 4 evaluation planning
 50- `result.go` - Layer 4 result types
 51- `actor_type.go` - Actor type definitions
 52- `document_example_test.go` - Layer 1 examples
 53- `test-data.go` - Shared test data
 54
 55### Package Organization Principles
 56
 571. **Single Import**: Consumers import one package: `github.com/ossf/gemara`
 58   With unified package, relationships are explicit:
 59   ```go
 60   // Cohesive - relationships clear
 61   import "github.com/ossf/gemara"
 62   
 63   var doc gemara.GuidanceDocument
 64   doc.Metadata = gemara.Metadata{...}  // Same namespace = clear relationship
 65   ```
 66
 67   The unified package makes it immediately obvious that `Metadata`, `GuidanceDocument`, `Catalog`, and `EvaluationLog` are all part of the same conceptual model, not separate concerns.
 68
 692. **Format Packages Separate**: Format converters remain separate packages (`oscal`, `sarif`) as they have different dependency profiles and are optional features
 70
 713. **Internal Utilities**: Shared implementation details go in `internal/`:
 72   - `internal/loaders/` - Generic file loading utilities
 73   - `internal/oscal/` - OSCAL-specific utilities
 74
 75## Consequences
 76
 77### Positive
 78
 791. **Simplified Imports**: Single import path for all Gemara functionality:
 80   ```go
 81   import "github.com/ossf/gemara"
 82   ```
 83
 842. **No Code Duplication**: Shared types and utilities defined once
 85
 863. **Easier Refactoring**: Changes to shared types automatically propagate throughout the codebase
 87
 884. **Simpler Schema Generation**: CUE generates one `generated_types.go` file, no manual splitting needed
 89
 905. **Unified API**: All functionality accessible through one package, improving discoverability
 91
 926. **Reduced Cognitive Load**: Users don't need to understand layer boundaries to use the library
 93
 94### Negative
 95
 961. **Larger Package**: Single package contains all functionality, which some may consider less organized
 97
 982. **Potential Namespace Pollution**: All exported types in one namespace (mitigated by clear naming conventions)
 99
1003. **Migration Effort**: Existing consumers needed to update imports (though straightforward find/replace)
101
102### Neutral
103
1041. **Documentation**: Package documentation can reference layers conceptually while types are unified
105
1062. **Testing**: Tests remain organized by functionality, not package boundaries
107
1083. **Schema Organization**: CUE schemas remain organized by layer (`schemas/layer-1.cue`, etc.)
109
110## Alternatives Considered
111
112### Alternative: Keep Layer Packages, Extract Common Types
113
114Create a `common/` package for shared types (e.g., `Metadata`, `Actor`, `Mapping`, `Date`), keep layer-specific code in layer packages.
115
116**Pros:**
117- Maintains conceptual alignment with the model
118- Clear separation of concerns
119- Solves type sharing problem - shared types defined once in `common/`
120- Reduces code duplication
121
122**Cons:**
123- Requires multiple imports (`layer1`, `layer2`, `common`)
124- Semantic clarity tradeoff: Shared types would be prefixed with `common.` (e.g., `common.Metadata`, `common.Actor`), which doesn't add meaning - "common" is organizational, not semantic
125- Converters still need multiple imports for cross-layer usage
126
127**Decision**: Rejected - while this solves the type sharing problem, the semantic clarity tradeoff was considered worse than the unified package approach. Types like `Metadata` and `Actor` are core Gemara concepts, not "common utilities" - they deserve the same namespace as layer-specific types. The unified package provides better semantic clarity (`gemara.Metadata` vs `common.Metadata`) while solving all the same problems.
128
129## References
130
131- [Go Package Design](https://go.dev/blog/package-names)
132- [Effective Go - Packages](https://go.dev/doc/effective_go#names)
133- [Gemara Model Documentation](/model)