Skip to content

Commit 5dfa209

Browse files
committed
docs: update refactoring plan with iterative approach
- Replace over-engineered trait-first approach with simple iteration - Focus on one collaborator per Tera template extraction - Discover patterns through experience before abstracting - Start with AnsibleTemplateRenderer, then TofuTemplateRenderer - Reevaluate design after concrete experience with collaborators
1 parent 396b6e6 commit 5dfa209

File tree

1 file changed

+286
-0
lines changed

1 file changed

+286
-0
lines changed
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
# Template Renderer Simplification Refactoring Plan
2+
3+
## 📋 Overview
4+
5+
**Problem**: Both `AnsibleTemplateRenderer` and `TofuTemplateRenderer` have too many responsibilities and hardcoded template-specific logic. With more Tera templates, they would become unmaintainable.
6+
7+
**Philosophy**: Discover the design step-by-step through iterative refactoring, rather than over-engineering upfront abstractions.
8+
9+
## 🎯 Goals
10+
11+
1. **Extract collaborators** for template-specific logic (one per Tera template)
12+
2. **Simplify renderers** by removing hardcoded template knowledge
13+
3. **Learn from experience** before creating abstractions
14+
4. **Validate incrementally** - each step should improve the design
15+
16+
## 🚀 Iterative Approach
17+
18+
### Phase 1: Ansible Template Renderer (`estimated: 3-4 hours`)
19+
20+
**Focus**: Extract collaborator for `inventory.yml.tera` template handling.
21+
22+
#### Step 1.1: Extract InventoryTemplateRenderer
23+
24+
- **File**: `src/ansible/inventory_template_renderer.rs`
25+
- **Purpose**: Handle all `inventory.yml.tera` specific logic
26+
- **Status**: ❌ Not Started
27+
28+
**What to extract from AnsibleTemplateRenderer:**
29+
30+
```rust
31+
// From: AnsibleTemplateRenderer::render_inventory_template()
32+
// To: InventoryTemplateRenderer
33+
34+
pub struct InventoryTemplateRenderer {
35+
template_manager: Arc<TemplateManager>,
36+
}
37+
38+
impl InventoryTemplateRenderer {
39+
pub fn render(
40+
&self,
41+
context: &InventoryContext,
42+
output_dir: &Path
43+
) -> Result<(), InventoryTemplateError> {
44+
// All the inventory.yml.tera specific logic
45+
// - Get template path
46+
// - Read template content
47+
// - Create File object
48+
// - Create InventoryTemplate
49+
// - Render to output file
50+
}
51+
}
52+
```
53+
54+
#### Step 1.2: Update AnsibleTemplateRenderer to use collaborator
55+
56+
- **File**: `src/ansible/template_renderer.rs`
57+
- **Purpose**: Compose with InventoryTemplateRenderer instead of handling directly
58+
- **Status**: ❌ Not Started
59+
60+
```rust
61+
pub struct AnsibleTemplateRenderer {
62+
build_dir: PathBuf,
63+
template_manager: Arc<TemplateManager>,
64+
inventory_renderer: InventoryTemplateRenderer, // NEW
65+
}
66+
67+
impl AnsibleTemplateRenderer {
68+
pub async fn render(&self, inventory_context: &InventoryContext) -> Result<(), ConfigurationTemplateError> {
69+
let build_ansible_dir = self.create_build_directory().await?;
70+
71+
// Use collaborator instead of hardcoded logic
72+
self.inventory_renderer.render(inventory_context, &build_ansible_dir)?;
73+
74+
self.copy_static_templates(&build_ansible_dir).await?;
75+
76+
Ok(())
77+
}
78+
}
79+
```
80+
81+
### Phase 2: OpenTofu Template Renderer (`estimated: 3-4 hours`)
82+
83+
**Focus**: Apply lessons learned to extract collaborator for `cloud-init.yml.tera`.
84+
85+
#### Step 2.1: Extract CloudInitTemplateRenderer
86+
87+
- **File**: `src/tofu/cloud_init_template_renderer.rs`
88+
- **Purpose**: Handle all `cloud-init.yml.tera` specific logic
89+
- **Status**: ❌ Not Started
90+
91+
```rust
92+
pub struct CloudInitTemplateRenderer {
93+
template_manager: Arc<TemplateManager>,
94+
}
95+
96+
impl CloudInitTemplateRenderer {
97+
pub fn render(
98+
&self,
99+
ssh_credentials: &SshCredentials,
100+
output_dir: &Path
101+
) -> Result<(), CloudInitTemplateError> {
102+
// All the cloud-init.yml.tera specific logic
103+
// - Get template path
104+
// - Read template content
105+
// - Create CloudInitContext from SSH credentials
106+
// - Create CloudInitTemplate
107+
// - Render to output file
108+
}
109+
}
110+
```
111+
112+
#### Step 2.2: Update TofuTemplateRenderer to use collaborator
113+
114+
- **File**: `src/tofu/template_renderer.rs`
115+
- **Purpose**: Compose with CloudInitTemplateRenderer instead of handling directly
116+
- **Status**: ❌ Not Started
117+
118+
```rust
119+
pub struct TofuTemplateRenderer {
120+
template_manager: Arc<TemplateManager>,
121+
build_dir: PathBuf,
122+
ssh_credentials: SshCredentials,
123+
cloud_init_renderer: CloudInitTemplateRenderer, // NEW
124+
}
125+
126+
impl TofuTemplateRenderer {
127+
pub async fn render(&self) -> Result<(), ProvisionTemplateError> {
128+
let build_tofu_dir = self.create_build_directory().await?;
129+
130+
self.copy_templates(&["main.tf"], &build_tofu_dir).await?;
131+
132+
// Use collaborator instead of hardcoded logic
133+
self.cloud_init_renderer.render(&self.ssh_credentials, &build_tofu_dir)?;
134+
135+
Ok(())
136+
}
137+
}
138+
```
139+
140+
### Phase 3: Design Reevaluation (`estimated: 1-2 hours`)
141+
142+
#### Step 3.1: Assess Current State
143+
144+
**Questions to answer:**
145+
146+
1. **Are the renderers now focused?** Do they only handle directory creation and static file copying?
147+
2. **Are the collaborators cohesive?** Does each handle exactly one Tera template?
148+
3. **Is there duplication?** Are there patterns emerging between the collaborators?
149+
4. **Do we need more extraction?** Are there still types with too many responsibilities?
150+
151+
#### Step 3.2: Decide Next Steps
152+
153+
**Possible outcomes:**
154+
155+
**Good enough**: If types are focused and maintainable, stop here
156+
🔄 **More extraction needed**: Extract more collaborators (e.g., for static file handling)
157+
🏗️ **Abstract patterns**: If 3+ similar collaborators exist, consider trait extraction
158+
159+
## 📊 Progress Tracking
160+
161+
### Phase 1: Ansible (0/2 completed)
162+
163+
- [ ] Extract InventoryTemplateRenderer collaborator
164+
- [ ] Update AnsibleTemplateRenderer to use collaborator
165+
166+
### Phase 2: OpenTofu (0/2 completed)
167+
168+
- [ ] Extract CloudInitTemplateRenderer collaborator
169+
- [ ] Update TofuTemplateRenderer to use collaborator
170+
171+
### Phase 3: Reevaluation (0/2 completed)
172+
173+
- [ ] Assess current design state
174+
- [ ] Decide on next iteration (if needed)
175+
176+
## 🎯 Success Criteria
177+
178+
**After Phase 1:**
179+
180+
- `AnsibleTemplateRenderer` no longer contains `render_inventory_template()` method
181+
- Inventory-specific logic is isolated in `InventoryTemplateRenderer`
182+
- All existing tests pass
183+
184+
**After Phase 2:**
185+
186+
- `TofuTemplateRenderer` no longer contains `render_cloud_init_template()` method
187+
- Cloud-init-specific logic is isolated in `CloudInitTemplateRenderer`
188+
- All existing tests pass
189+
190+
**After Phase 3:**
191+
192+
- Clear decision on whether further refactoring is needed
193+
- Documentation updated with lessons learned
194+
195+
## 🔍 What We'll Learn
196+
197+
### From Ansible Refactoring:
198+
199+
- How to cleanly separate template-specific logic
200+
- What the collaborator interface should look like
201+
- Error handling patterns
202+
- Testing strategies
203+
204+
### From OpenTofu Refactoring:
205+
206+
- Whether the Ansible patterns work for different contexts
207+
- If there are common patterns worth abstracting
208+
- Edge cases we missed in the first iteration
209+
210+
### From Reevaluation:
211+
212+
- Whether this level of separation is sufficient
213+
- If trait extraction would actually help
214+
- What other responsibilities might need extraction
215+
216+
## 🚫 What We're NOT Doing
217+
218+
-**No traits initially** - Discover patterns first
219+
-**No generic renderers** - Keep it concrete
220+
-**No complex error hierarchies** - Simple, focused errors
221+
-**No over-abstraction** - Extract only what we actually need
222+
223+
## 📝 Implementation Notes
224+
225+
### Error Handling Strategy
226+
227+
Keep errors simple and focused per collaborator:
228+
229+
```rust
230+
// For InventoryTemplateRenderer
231+
#[derive(Error, Debug)]
232+
pub enum InventoryTemplateError {
233+
#[error("Failed to read inventory template: {0}")]
234+
TemplateReadError(#[from] std::io::Error),
235+
236+
#[error("Failed to render inventory template: {0}")]
237+
RenderError(#[from] crate::template::TemplateEngineError),
238+
239+
// etc.
240+
}
241+
```
242+
243+
### Constructor Changes
244+
245+
Update constructors to include collaborators:
246+
247+
```rust
248+
impl AnsibleTemplateRenderer {
249+
pub fn new(build_dir: PathBuf, template_manager: Arc<TemplateManager>) -> Self {
250+
let inventory_renderer = InventoryTemplateRenderer::new(template_manager.clone());
251+
252+
Self {
253+
build_dir,
254+
template_manager,
255+
inventory_renderer,
256+
}
257+
}
258+
}
259+
```
260+
261+
### Testing Strategy
262+
263+
Test collaborators in isolation:
264+
265+
```rust
266+
#[cfg(test)]
267+
mod tests {
268+
use super::*;
269+
270+
#[test]
271+
fn inventory_renderer_should_render_template() {
272+
let renderer = InventoryTemplateRenderer::new(mock_template_manager());
273+
let context = mock_inventory_context();
274+
let temp_dir = TempDir::new().unwrap();
275+
276+
let result = renderer.render(&context, temp_dir.path());
277+
278+
assert!(result.is_ok());
279+
assert!(temp_dir.path().join("inventory.yml").exists());
280+
}
281+
}
282+
```
283+
284+
---
285+
286+
**Next Steps**: Start with Phase 1.1 - Extract `InventoryTemplateRenderer` from `AnsibleTemplateRenderer`.

0 commit comments

Comments
 (0)