(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