Skip to content

Commit a292874

Browse files
committed
Refactor Value class and backends to use unified path-based approach
- Remove storage_type from Value class - Update ValueBackend interface to use path and environment - Add SimpleValueBackend for non-sensitive values - Update test cases and documentation - Add ADR for unified backend approach
1 parent dd203a9 commit a292874

File tree

9 files changed

+465
-253
lines changed

9 files changed

+465
-253
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# ADR-005: Unified Backend Approach for Value Storage
2+
3+
## Status
4+
Proposed
5+
6+
## Context
7+
Currently, the Value class handles two distinct storage types: local and remote. This creates a split in logic within the Value class, requiring different code paths and validation rules based on the storage type. This complexity makes the code harder to maintain and test.
8+
9+
## Decision
10+
We will remove the storage_type distinction from the Value class and implement a SimpleValueBackend for non-sensitive data (previously handled as "local" storage). This means:
11+
12+
1. Value class will:
13+
- Only interact with backends through a unified interface
14+
- Not need to know about storage types
15+
- Have simpler logic and better separation of concerns
16+
17+
2. Storage backends will:
18+
- Include a new SimpleValueBackend for non-sensitive data
19+
- All implement the same ValueBackend interface
20+
- Be responsible for their specific storage mechanisms
21+
22+
3. Configuration will:
23+
- Use SimpleValueBackend internally for non-sensitive values
24+
- Use secure backends (AWS/Azure) for sensitive values
25+
- Backend selection handled by the system based on value sensitivity
26+
27+
## Consequences
28+
29+
### Positive
30+
1. **Cleaner Value Class**
31+
- Removes storage type logic
32+
- Single consistent interface for all operations
33+
- Better separation of concerns
34+
35+
2. **Unified Testing**
36+
- All storage types tested through the same interface
37+
- No need for separate local/remote test cases
38+
- Easier to mock and verify behavior
39+
40+
3. **More Flexible**
41+
- Easy to add new backend types
42+
- Consistent interface for all storage types
43+
- Clear extension points
44+
45+
4. **Better Security Model**
46+
- Storage backend choice driven by data sensitivity
47+
- Clear separation between sensitive and non-sensitive data
48+
- Explicit in configuration
49+
50+
### Negative
51+
1. **Slight Performance Impact**
52+
- Additional method calls for simple value operations
53+
- Extra object creation for SimpleValueBackend
54+
55+
### Neutral
56+
1. **Configuration Changes**
57+
- Backend selection based on value sensitivity
58+
- Transparent to end users
59+
60+
## Implementation Plan
61+
62+
1. **Backend Development**
63+
```python
64+
class SimpleValueBackend(ValueBackend):
65+
def __init__(self):
66+
self._values = {}
67+
68+
def get_value(self, path: str, environment: str) -> str:
69+
key = self._make_key(path, environment)
70+
return self._values[key]
71+
72+
def set_value(self, path: str, environment: str, value: str) -> None:
73+
key = self._make_key(path, environment)
74+
self._values[key] = value
75+
```
76+
77+
2. **Value Class Simplification**
78+
```python
79+
@dataclass
80+
class Value:
81+
path: str
82+
environment: str
83+
_backend: ValueBackend
84+
85+
def get(self) -> str:
86+
return self._backend.get_value(self.path, self.environment)
87+
88+
def set(self, value: str) -> None:
89+
if not isinstance(value, str):
90+
raise ValueError("Value must be a string")
91+
self._backend.set_value(self.path, self.environment, value)
92+
```
93+
94+
3. **Configuration Example**
95+
```json
96+
{
97+
"version": "1.0",
98+
"release": "my-app",
99+
"deployments": {
100+
"prod": {
101+
"backend": "aws",
102+
"auth": {
103+
"type": "managed_identity"
104+
},
105+
"backend_config": {
106+
"region": "us-west-2"
107+
}
108+
}
109+
},
110+
"config": [
111+
{
112+
"path": "app.replicas",
113+
"description": "Number of application replicas",
114+
"required": true,
115+
"sensitive": false,
116+
"values": {
117+
"dev": "3",
118+
"prod": "5"
119+
}
120+
},
121+
{
122+
"path": "app.secretKey",
123+
"description": "Application secret key",
124+
"required": true,
125+
"sensitive": true,
126+
"values": {
127+
"dev": "dev-key-123",
128+
"prod": "prod-key-456"
129+
}
130+
}
131+
]
132+
}
133+
```
134+
135+
The system will automatically:
136+
- Use SimpleValueBackend for non-sensitive values (app.replicas)
137+
- Use configured secure backend for sensitive values (app.secretKey)

docs/Design/low-level-design.md

Lines changed: 54 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,11 @@ classDiagram
2828
class Value {
2929
+str path
3030
+str environment
31-
+str storage_type
3231
-ValueBackend _backend
33-
-Optional~str~ _value
3432
+get() str
3533
+set(value: str) None
3634
+to_dict() dict
37-
+from_dict(data: dict, path: str, environment: str, backend: ValueBackend) Value
35+
+from_dict(data: dict, backend: ValueBackend) Value
3836
}
3937
4038
class ConfigValue {
@@ -49,6 +47,7 @@ classDiagram
4947
+str name
5048
+Dict~str,Any~ auth
5149
+str backend
50+
+Dict~str,Any~ backend_config
5251
}
5352
5453
class BaseCommand {
@@ -61,30 +60,37 @@ classDiagram
6160
6261
class ValueBackend {
6362
<<interface>>
64-
+get_value(key: str)* str
65-
+set_value(key: str, value: str)* None
63+
+get_value(path: str, environment: str)* str
64+
+set_value(path: str, environment: str, value: str)* None
6665
+validate_auth_config(auth_config: dict)* None
6766
}
6867
68+
class SimpleValueBackend {
69+
-Dict~str,str~ values
70+
+get_value(path: str, environment: str) str
71+
+set_value(path: str, environment: str, value: str) None
72+
}
73+
6974
class AWSSecretsBackend {
7075
-SecretsManagerClient client
71-
+get_value(key: str) str
72-
+set_value(key: str, value: str) None
76+
+get_value(path: str, environment: str) str
77+
+set_value(path: str, environment: str, value: str) None
7378
+validate_auth_config(auth_config: dict) None
7479
}
7580
7681
class AzureKeyVaultBackend {
7782
-KeyVaultClient client
78-
+get_value(key: str) str
79-
+set_value(key: str, value: str) None
83+
+get_value(path: str, environment: str) str
84+
+set_value(path: str, environment: str, value: str) None
8085
+validate_auth_config(auth_config: dict) None
8186
}
8287
8388
HelmValuesConfig "1" *-- "*" ConfigValue
8489
HelmValuesConfig "1" *-- "*" Deployment
8590
HelmValuesConfig "1" *-- "*" PathData
8691
PathData "1" *-- "*" Value
87-
Value "1" o-- "0..1" ValueBackend
92+
Value "1" o-- "1" ValueBackend
93+
ValueBackend <|.. SimpleValueBackend
8894
ValueBackend <|.. AWSSecretsBackend
8995
ValueBackend <|.. AzureKeyVaultBackend
9096
BaseCommand <|-- SetValueCommand
@@ -98,36 +104,25 @@ The system uses a unified approach to value storage and resolution through the `
98104
```python
99105
class Value:
100106
def __init__(self, path: str, environment: str,
101-
storage_type: str = "local",
102-
backend: Optional[ValueBackend] = None):
107+
backend: ValueBackend):
103108
self.path = path
104109
self.environment = environment
105-
self.storage_type = storage_type
106110
self._backend = backend
107-
self._value: Optional[str] = None
108111

109112
def get(self) -> str:
110113
"""Get the resolved value"""
111-
if self.storage_type == "local":
112-
return self._value
113-
return self._backend.get_value(
114-
self._generate_key(self.path, self.environment)
115-
)
114+
return self._backend.get_value(self.path, self.environment)
116115

117116
def set(self, value: str) -> None:
118117
"""Set the value"""
119-
if self.storage_type == "local":
120-
self._value = value
121-
else:
122-
self._backend.set_value(
123-
self._generate_key(self.path, self.environment),
124-
value
125-
)
118+
if not isinstance(value, str):
119+
raise ValueError("Value must be a string")
120+
self._backend.set_value(self.path, self.environment, value)
126121
```
127122

128123
Key features:
129124
- Encapsulated value resolution logic
130-
- Unified interface for local and remote storage
125+
- Unified interface for all storage backends
131126
- Clear separation of concerns
132127
- Type-safe value handling
133128

@@ -174,13 +169,13 @@ The `ValueBackend` interface defines the contract for value storage:
174169
```python
175170
class ValueBackend(ABC):
176171
@abstractmethod
177-
def get_value(self, key: str) -> str:
178-
"""Get a value from storage using a unique key."""
172+
def get_value(self, path: str, environment: str) -> str:
173+
"""Get a value from storage."""
179174
pass
180175

181176
@abstractmethod
182-
def set_value(self, key: str, value: str) -> None:
183-
"""Store a value using a unique key."""
177+
def set_value(self, path: str, environment: str, value: str) -> None:
178+
"""Store a value."""
184179
pass
185180

186181
@abstractmethod
@@ -190,6 +185,7 @@ class ValueBackend(ABC):
190185
```
191186

192187
Implementations:
188+
- SimpleValueBackend (for non-sensitive values)
193189
- AWS Secrets Manager Backend
194190
- Azure Key Vault Backend
195191
- Additional backends can be easily added
@@ -198,22 +194,34 @@ Implementations:
198194

199195
### 1. Configuration Structure
200196

201-
The configuration is stored in a hierarchical structure:
202-
203-
```python
197+
The configuration follows the v1 schema:
198+
```json
204199
{
205-
"app.replicas": {
206-
"metadata": {
207-
"description": "Number of replicas",
208-
"required": True,
209-
"sensitive": False
210-
},
211-
"values": {
212-
"dev": Value(path="app.replicas", environment="dev", storage_type="local"),
213-
"prod": Value(path="app.replicas", environment="prod",
214-
storage_type="aws", backend=aws_backend)
200+
"version": "1.0",
201+
"release": "my-app",
202+
"deployments": {
203+
"prod": {
204+
"backend": "aws",
205+
"auth": {
206+
"type": "managed_identity"
207+
},
208+
"backend_config": {
209+
"region": "us-west-2"
210+
}
215211
}
216-
}
212+
},
213+
"config": [
214+
{
215+
"path": "app.replicas",
216+
"description": "Number of application replicas",
217+
"required": true,
218+
"sensitive": false,
219+
"values": {
220+
"dev": "3",
221+
"prod": "5"
222+
}
223+
}
224+
]
217225
}
218226
```
219227

@@ -226,7 +234,7 @@ The configuration is stored in a hierarchical structure:
226234

227235
2. Value Resolution:
228236
- Uses `Value` class to handle resolution
229-
- Automatically selects local or remote storage
237+
- Automatically selects storage backend
230238
- Handles errors and validation
231239

232240
### 3. Security Features

0 commit comments

Comments
 (0)