1
\$\begingroup\$

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.

\$\endgroup\$
1

1 Answer 1

3
\$\begingroup\$

A few suggestions in the code. Use Dependency Injection for better test-ability

  1. In Controller

    public class Controller
    {
        private readonly IRepository _repository = null;
        public Controller(IRepository repository)
        {
            this._repository = repository;
        } 
     }
    
  2. In Repository

    public class Repository 
    {
        private readonly IService _service;
    
        public Repository(IService service)
        {
            this.service = service;
        }
    
  3. In Flex session

    public class FlexSession
    {
        public FlexSession(IDocument flexSessionDocument)
        {
          ......
        }
    }
    
\$\endgroup\$
1
  • \$\begingroup\$ Thank you for the feedback! Since the document is a POCO, is there any benefit to adding IDocument? \$\endgroup\$
    – Sam
    Apr 12, 2018 at 14:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.