(I am facing this issue with code written in Swift, but would appreciate any high-level pseudocode solution, just so that I may wrap my head around the architecture)
I need to find an architecture that would allow me to store generic data into instances of a specific type.
Consider the following example:
struct Project: Codable { let id: String let title: String // ... other metadata } protocol Publisher { associatedtype Configuration: Codable func publish(project: Project, using configuration: Configuration) } class MyPublisher { struct Configuration: Codable { let destination: String // ... other configuration data } func publish(project: Project, using configuration: Configuration) {} }
How could I design the Project
type to store any arbitrary Configuration
types?
I have the following requirements:
Project
isCodable
, so any data it stores should also conform toCodable
Project
does not care about the actual data stored, as it is only destined forPublisher
types.- There may be 0 to many publisher configuration stored
I have found the following subpar solutions:
Storing static references to existing Publisher types
struct Project: Codable { // ... metadata var myPublisherConfiguration: MyPublisher.Configuration? // potentially 5 other such variables for other available publishers }
I want to avoid doing this because this strongly couples the Project
type to any and all Publisher
types (that, and I cannot do this as it stands as Publishers exist in different packages, can be added externally and should not require changes to the Project
type every time).
Adding a layer between Project
and Publisher
Another solution would be to create a Manager
type that would oversee linking Project
and Publisher
. Something like:
struct PublisherManager: Codable { var publisherTypes: [any Publisher.Type] = [] var storedConfigurations: [String: [any Publisher.Configuration]] = [:] func registerPublisher(_ publisherType: Publisher.Type) { // do some more validation, but for the example just add publisherTypes.append(publisherType) } func configuration<T: Publisher>(for project: Project, publisherType: T.Type) -> T.Configuration? { guard let configurations = storedConfiguration[project.id] else { return nil } return configurations.compactMap { config in config as? T.Configuration).first } }
This is just a gist, and probably doesn't even compile due to the use of any Publisher.Configuration
, as the compiler doesn't know how to encode/decode this... But there are issues with this solution anyways, even if I got it to work;
- It splits up where data is stored, which requires manual work to keep everything in sync (think deleting a project, it should also delete all stored configurations for that project)
What is the best approach?
I need to find a solution that is simple, which allows me to persist generic data related to objects, without those objects needing to manage and/or care about the data persisted.
Is this even a good approach? Are there better architectural paradigms I'm overlooking to achieve something similar?
Any help would be greatly appreciated 🙏