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)