Decouples score definitions from UI implementations by mandating `serde::Serialize` and `serde::Deserialize` for all `Score` structs. UIs will interact with scores via their serialized representation, enabling scalability and reducing complexity for score authors. This approach: - Scales better with new score types and UI targets. - Simplifies score authoring by removing the need for UI-specific display traits. - Leverages the `serde` ecosystem for robust data handling. Adding new field types requires updates to all UIs, a trade-off acknowledged in the ADR.
		
			
				
	
	
		
			63 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			63 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| ## Architecture Decision Record: Data Representation and UI Rendering for Score Types
 | ||
| 
 | ||
| **Status:** Proposed
 | ||
| 
 | ||
| **TL;DR:** `Score` types will be serialized (using `serde`) for presentation in UIs. This decouples data definition from presentation, improving scalability and reducing complexity for developers defining `Score` types. New UI types only need to handle existing field types, and new `Score` types don’t require UI changes as long as they use existing field types.  Adding a new field type *does* require updates to all UIs.
 | ||
| 
 | ||
| **Key benefits:** Scalability, reduced complexity for `Score` authors, decoupling of data and presentation.
 | ||
| 
 | ||
| **Key trade-off:** Adding new field types requires updating all UIs.
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| **Context:**
 | ||
| 
 | ||
| Harmony is a pure Rust infrastructure orchestrator focused on compile-time safety and providing a developer-friendly, Ansible-module-like experience for defining infrastructure configurations via "Scores". These Scores (e.g., `LAMPScore`) are Rust structs composed of specific, strongly-typed fields (e.g., `VersionField`, `UrlField`, `PathField`) which are validated at compile-time using macros (`Version!`, `Url!`, etc.).
 | ||
| 
 | ||
| A key requirement is displaying the configuration defined in these Scores across various user interfaces (Web UI, TUI, potentially Mobile UI, etc.) in a consistent and type-safe manner. As the number of Score types is expected to grow significantly (hundreds or thousands), we need a scalable approach for rendering their data that avoids tightly coupling Score definitions to specific UI implementations.
 | ||
| 
 | ||
| The primary challenge is preventing the need for every `Score` struct author to implement multiple display traits (e.g., `Display`, `WebDisplay`, `TuiDisplay`) for every potential UI target. This would create an N x M complexity problem (N Scores * M UI types) and place an unreasonable burden on Score developers, hindering scalability and maintainability.
 | ||
| 
 | ||
| **Decision:**
 | ||
| 
 | ||
| 1.  **Mandatory Serialization:** All `Score` structs *must* implement `serde::Serialize` and `serde::Deserialize`. They *will not* be required to implement `std::fmt::Display` or any custom UI-specific display traits (e.g., `WebDisplay`, `TuiDisplay`).
 | ||
| 2.  **Field-Level Rendering:** Responsibility for rendering data will reside within the UI components. Each UI (Web, TUI, etc.) will implement logic to display *individual field types* (e.g., `UrlField`, `VersionField`, `IpAddressField`, `SecretField`).
 | ||
| 3.  **Data Access via Serialization:** UIs will primarily interact with `Score` data through its serialized representation (e.g., JSON obtained via `serde_json`). This provides a standardized interface for UIs to consume the data structure agnostic of the specific `Score` type. Alternatively, UIs *could* potentially use reflection or specific visitor patterns on the `Score` struct itself, but serialization is the preferred decoupling mechanism.
 | ||
| 
 | ||
| **Rationale:**
 | ||
| 
 | ||
| 1.  **Decoupling Data from Presentation:** This decision cleanly separates the data definition (`Score` structs and their fields) from the presentation logic (UI rendering). `Score` authors can focus solely on defining the data and its structure, while UI developers focus on how to best present known data *types*.
 | ||
| 2.  **Scalability:** This approach scales significantly better than requiring display trait implementations on Scores:
 | ||
|     *   Adding a *new Score type* requires *no changes* to existing UI code, provided it uses existing field types.
 | ||
|     *   Adding a *new UI type* requires implementing rendering logic only for the defined set of *field types*, not for every individual `Score` type. This reduces the N x M complexity to N + M complexity (approximately).
 | ||
| 3.  **Simplicity for Score Authors:** Requiring only `serde::Serialize + Deserialize` (which can often be derived automatically with `#[derive(Serialize, Deserialize)]`) is a much lower burden than implementing custom rendering logic for multiple, potentially unknown, UI targets.
 | ||
| 4.  **Leverages Rust Ecosystem Standards:** `serde` is the de facto standard for serialization and deserialization in Rust. Relying on it aligns with common Rust practices and benefits from its robustness, performance, and extensive tooling.
 | ||
| 5.  **Consistency for UIs:** Serialization provides a consistent, structured format (like JSON) for UIs to consume data, regardless of the underlying `Score` struct's complexity or composition.
 | ||
| 6.  **Flexibility for UI Implementation:** UIs can choose the best way to render each field type based on their capabilities (e.g., a `UrlField` might be a clickable link in a Web UI, plain text in a TUI; a `SecretField` might be masked).
 | ||
| 
 | ||
| **Consequences:**
 | ||
| 
 | ||
| **Positive:**
 | ||
| 
 | ||
| *   Greatly improved scalability for adding new Score types and UI targets.
 | ||
| *   Strong separation of concerns between data definition and presentation.
 | ||
| *   Reduced implementation burden and complexity for Score authors.
 | ||
| *   Consistent mechanism for UIs to access and interpret Score data.
 | ||
| *   Aligns well with the Hexagonal Architecture (ADR-002) by treating UIs as adapters interacting with the application core via a defined port (the serialized data contract).
 | ||
| 
 | ||
| **Negative:**
 | ||
| 
 | ||
| *   Adding a *new field type* (e.g., `EmailField`) requires updates to *all* existing UI implementations to support rendering it.
 | ||
| *   UI components become dependent on the set of defined field types and need comprehensive logic to handle each one appropriately.
 | ||
| *   Potential minor overhead of serialization/deserialization compared to direct function calls (though likely negligible for UI purposes).
 | ||
| *   Requires careful design and management of the standard library of field types.
 | ||
| 
 | ||
| **Alternatives Considered:**
 | ||
| 
 | ||
| 1.  **`Score` Implements `std::fmt::Display`:**
 | ||
|     *   _Rejected:_ Too simplistic. Only suitable for basic text rendering, doesn't cater to structured UIs (Web, etc.), and doesn't allow type-specific rendering logic (e.g., masking secrets). Doesn't scale to multiple UI formats.
 | ||
| 2.  **`Score` Implements Multiple Custom Display Traits (`WebDisplay`, `TuiDisplay`, etc.):**
 | ||
|     *   _Rejected:_ Leads directly to the N x M complexity problem. Tightly couples Score definitions to specific UI implementations. Places an excessive burden on Score authors, hindering adoption and scalability.
 | ||
| 3.  **Generic Display Trait with Context (`Score` implements `DisplayWithContext<UIContext>`):**
 | ||
|     *   _Rejected:_ More flexible than multiple traits, but still requires Score authors to implement potentially complex rendering logic within the `Score` definition itself. The `Score` would still need awareness of different UI contexts, leading to undesirable coupling. Managing context types adds complexity.
 |