I'm trying to introduce immutable types into our code base and I'm trying to determine a good pattern that is intuitive for our team. I was reading about the Popsicle pattern and that seemed complicated. I also saw a lot of builder patterns. But those seemed like overkill as well. In the example below I would like to make the FlexSession object immutable to guarantee it is always in a valid state.
The immutable part I'm particularly trying to determine a good pattern is in the Controller below. Since FlexSession is immutable, I added a Create function that will clone the FlexSession object and in doing so will update the properties. I've seen other examples of using builders to construct the new object, but I struggled to see how that might work to compare the merits of the other pattern.
My basic architecture is an MVC controller calls a repository to query for an object. The controller updates the object and sends the object back into the repository to persist the changes.
In the repository there is a service that talks with DynamoDb. I am considering putting a conversion from the document object to a domain object. Where the domain object is immutable.
DynamoDb Document
public class FlexSessionDocument
{
public string SortKey { get; set; }
public List<int> PriceTypes { get; set; }
}
Repository
public class Repository
{
private readonly Service service;
public Repository(Service service)
{
this.service = service;
}
public FlexSession Get(string sessionKey, string packageId, string token)
{
var query = BuildQueryByKeys(sessionKey, packageId, token);
var model = this.service.Get<FlexSessionDocument>(query);
return new FlexSession(model);
}
public void Update(FlexSession session)
{
var sortKey = BuildKey(session);
var model = new FlexSessionDocument();
model.SortKey = sortKey;
model.PriceTypes = session.PriceTypes.ToList();
this.service.Put(model);
}
private Query BuildQueryByKeys(string sessionKey, string packageId, string token)
{
...
}
private string BuildKey(FlexSession session)
{
return session.SessionKey + "|" + session.PackageId + "|" + session.Token;
}
}
Controller
public class Controller
{
private readonly Repository repository;
public Controller(Repository repository)
{
this.repository = repository;
}
public ActionResult Put(string sessionKey, string packageId, string token, int[] priceTypes)
{
var flexSession = this.repository.Get(sessionKey, packageId, token);
var newFlexSession = flexSession.Create(priceTypes);
this.repository.Update(newFlexSession);
return Ok();
}
}
Domain Model
public class FlexSession
{
public FlexSession(FlexSessionDocument document)
{
var parts = ParseSortKey(document.SortKey);
SessionKey = parts.Item1;
PackageId = int.Parse(parts.Item2);
Token = parts.Item3;
PriceTypes = document.PriceTypes == null ? ImmutableList.Create<int>() : ImmutableList.Create(document.PriceTypes.ToArray());
}
private FlexSession(string sessionKey, int packageId, string token, ImmutableList<int> priceTypes)
{
SessionKey = sessionKey;
PackageId = packageId;
Token = token;
PriceTypes = priceTypes;
}
public string SessionKey { get; }
public int PackageId { get; }
public string Token { get; }
public ImmutableList<int> PriceTypes { get; }
public FlexSession Create(int[] priceTypes)
{
if (priceTypes.Length > 100)
{
throw new Exception("Invalid price type quanity");
}
var result = new FlexSession(SessionKey, PackageId, Token, ImmutableList.Create(priceTypes));
return result;
}
private Tuple<string, string, string> ParseSortKey(string sortKey)
{
if (string.IsNullOrWhiteSpace(sortKey))
{
throw new Exception($"Invalid flex session sort key value: {sortKey}");
}
var keyparts = sortKey.Split('|');
if (keyparts.Length != 3)
{
throw new Exception($"Failed to split the flex session sort key. Value: {sortKey}");
}
if (string.IsNullOrWhiteSpace(keyparts[0]))
{
throw new Exception($"Invalid flex session sort key, tessitura session key was missing. Value: {sortKey}");
}
var sessionKey = keyparts[0];
if (string.IsNullOrWhiteSpace(keyparts[1]))
{
throw new Exception($"Invalid flex session sort key, package id was missing. Value: {sortKey}");
}
var packageId = keyparts[1];
if (string.IsNullOrWhiteSpace(keyparts[2]))
{
throw new Exception($"Invalid flex session sort key, flex token was missing. Value: {sortKey}");
}
var token = keyparts[2];
return Tuple.Create(sessionKey, packageId, token);
}
}
I saw the ImmutableList has an "Add" function that will return a new list with the new data. Instead of "Create" would a function named "Update" be more appropriate.