Architecture decisions¶
Records of the design decisions behind HELMshaker, and why the alternatives were rejected.
ADR 001: Choose Stateful Molecule Entity with Mutable Internal Data¶
Status¶
Accepted
Context¶
We need a core entity to represent an oligonucleotides, within the package. This entity must support fundamental operations such as parsing from various formats, conversion to other formats, chemical modifications, visualization, and validation.
A common usage pattern for the library is expected to involve a sequence of operations applied to a single molecule instance, which is naturally expressed using a fluent interface (method chaining) like molecule.modify(...).validate()....
We have considered two primary design paradigms for the core molecule entity and its underlying data structure:
- Stateful Object with Mutable Internal Data: The
Moleculeobject holds a mutable representation of the molecule (MoleculeData), and operations modify this representation in-place, returning theMoleculeinstance (self) to enable chaining. - Immutable Data with New Object on Modification: The core data structure (
MoleculeData) is immutable. Operations that conceptually modify the molecule return a newMoleculeinstance containing the updated immutable data.
Decision¶
We will implement the core Molecule class as a stateful object that holds a mutable internal MoleculeData structure.
Modification methods (modify, etc.) and the validate method will operate by changing the self._molecule_data instance in place and will return self to support method chaining.
The MoleculeData structure itself will be defined using standard mutable Python collections (list, dict) within its dataclass fields (i.e., dataclasses(frozen=False)).
To maintain encapsulation and prevent external code from directly modifying the internal _molecule_data state managed by the Molecule object's methods, the @property data used for accessing the internal representation will return a deep copy of the internal _molecule_data.
Consequences¶
-
Positive:
- User Experience: This design aligns perfectly with and directly supports the intuitive fluent interface demonstrated in anticipated usage scripts (
molecule.modify(...).validate()...). This is a common and user-friendly pattern for sequential operations on a single subject. - Implementation Familiarity: Using standard mutable Python collections (
list,dict) inMoleculeDatais familiar and straightforward for developers implementing the parsing and modification services, which often involve building and manipulating collections. - Encapsulation: By returning a deep copy from the public
.dataproperty, we effectively prevent external, uncontrolled modification of the internal state, mitigating the primary risk associated with using mutable internal data. All intended modifications must go through theMoleculeobject's methods, ensuring associated logic (like re-validation) is triggered.
- User Experience: This design aligns perfectly with and directly supports the intuitive fluent interface demonstrated in anticipated usage scripts (
-
Negative:
- Internal Mutability Risk: The internal
_molecule_datais still mutable. While protected by the public API returning copies, this places a reliance on internal code discipline within theMoleculeclass and its services to avoid unintended internal side effects or accidental exposure of the live mutable object. - Working with Multiple Versions: Creating a new version of a molecule explicitly requires copying the
Moleculeobject (mol2 = copy.deepcopy(mol1)), which is less idiomatic than modification methods naturally returning a new instance (as in the immutable data approach). - Performance Overhead on Data Access: Accessing the underlying data via the
.dataproperty incurs the performance cost of a deep copy every time it is called. For very large molecule data structures, this could potentially become a performance bottleneck, requiring future optimization (e.g., providing alternative access methods, considering lighter-weight views).
- Internal Mutability Risk: The internal
This decision prioritizes user ergonomics through the fluent interface while implementing safeguards (deep copy on .data) to address the encapsulation challenges inherent in using mutable internal data.