7
\$\begingroup\$

I've been working on converting some legacy code from T-SQL stored procedures. My aim is to use DDD to make the transition. Below is an attempt to do this. Any comments on how well this is following DDD? Also any comments on the AreaGeneration domain service and its CalculateLocationsInArea would be most appreciated.

    //consumer code

var locationRepository = new LocationRepository();
//areaGenerator is a domain service which is used to calculate locations between a from and to location
var areaGenerator = new AreaGenerator(locationRepository);
var areaParameters = new List<AreaParameter>
    { 
        new AreaParameter("A001A", "A002B", AreaDirection.Column),
        new AreaParameter("A001A", "A003C", AreaDirection.Row)
    };
var locations = areaGenerator.CalculateLocationsInArea(areaParameters);
var area = Area.CreateArea("Test Area", "Special Area",areaParameters,locations);

public class Location
{
    public string LocationCode { get; set; }
    public int LocationID { get; set; }
    public int Aisle { get; set; }
    public int Column { get; set; }
    public int Row { get; set; }

    public string SortKey
    {
        get { return Aisle + "," + Column + "," + Row; }
    }
}

public class AreaParameter
{
    public AreaParameter(string fromLocation, string toLocation, AreaDirection areaDirection)
    {
        StartLocation = fromLocation;
        EndLocation = toLocation;
        AreaDirecton = areaDirection;
    }

    public string StartLocation { get; private set; }
    public string EndLocation { get; private set; }
    public AreaDirection AreaDirecton { get; private set; }
}


public class AreaGenerator
{
    private readonly ILocationRepository _locationRepository;

    public AreaGenerator(ILocationRepository locationRepository)
    {
        _locationRepository = locationRepository;
    }

    internal IEnumerable<Location> CalculateLocationsInArea(List<AreaParameter> areaParameters)
    {
        var locationsInArea = new List<Location>();

        foreach (var areaDetail in areaParameters)
        {
            var startLocation = _locationRepository.GetLocationByCode(areaDetail.StartLocation);
            var endLocation = _locationRepository.GetLocationByCode(areaDetail.EndLocation);

            var startAisle = startLocation.Aisle;
            var startColumn = startLocation.Column;
            var startRow = startLocation.Row;

            var endAisle = endLocation.Aisle;
            var endColumn = endLocation.Column;
            var endRow = endLocation.Row;


            switch (areaDetail.AreaDirecton)
            {
                case AreaDirection.Row:
                    if (startAisle == endAisle)
                    {
                        Func<Location, bool> withinAisleAndColAndNotLastRow =
                            l =>
                            ((l.Aisle == startAisle && l.Column >= startColumn && l.Row == startRow) &&
                             startRow != endRow)
                            ||
                            ((l.Aisle == startAisle && l.Column <= endColumn && l.Row == endRow) &&
                             startRow != endRow);
                        Func<Location, bool> withinAisleAndColAndIsLastRow =
                            l =>
                            ((l.Aisle == startAisle && l.Column >= startColumn && l.Column <= endColumn &&
                              l.Row == startRow) && startRow == endRow)
                            ||
                            ((l.Aisle == startAisle && l.Column >= startColumn && l.Column <= endColumn &&
                              l.Row == endRow) && startRow == endRow);
                        Func<Location, bool> inBetweener =
                            l => l.Aisle == startAisle && l.Row > startRow && l.Row < endRow;

                        var results =
                            _locationRepository.GetLocations(
                                l =>
                                withinAisleAndColAndNotLastRow(l) || withinAisleAndColAndIsLastRow(l) ||
                                inBetweener(l));
                        locationsInArea.AddRange(results);
                    }
                    else
                    {
                        Func<Location, bool> withinAisleAndColAndIsLastRow =
                            l => (l.Aisle == startAisle && l.Column >= startColumn && l.Row == startRow)
                                 || (l.Aisle == endAisle && l.Column <= endColumn && l.Row == endRow);
                        Func<Location, bool> withinAisleAndColAndNotLastRow =
                            l =>
                            ((l.Aisle == startAisle && l.Row > startRow) || (l.Aisle == endAisle && l.Row < endRow));
                        Func<Location, bool> inBetweener = l => l.Aisle > startAisle && l.Aisle < endAisle;

                        var results =
                            _locationRepository.GetLocations(
                                l =>
                                withinAisleAndColAndNotLastRow(l) || withinAisleAndColAndIsLastRow(l) ||
                                inBetweener(l));
                        locationsInArea.AddRange(results);
                    }
                    break;
                case AreaDirection.Column:
                    Func<Location, bool> predicate1 =
                        l => ((l.Aisle == startAisle && l.Row >= startRow && l.Column == startColumn)
                              || (l.Aisle > startAisle && l.Aisle < endAisle && l.Column == startColumn));
                    Func<Location, bool> predicate2 =
                        l =>
                        ((l.Aisle >= startAisle & l.Aisle <= endAisle &&
                          l.Column > startColumn & l.Column < endColumn)
                         || (l.Aisle == endAisle && l.Row <= endRow && l.Column == endColumn));
                    var locations = _locationRepository.GetLocations(l => predicate1(l) || predicate2(l));
                    locationsInArea.AddRange(locations);
                    break;
                case AreaDirection.ALL:
                    var allLocations =
                        _locationRepository.GetLocations(
                            l =>
                            l.SortKey.CompareTo(startLocation.SortKey) >= 0 &&
                            l.SortKey.CompareTo(endLocation.SortKey) <= 0);
                    locationsInArea.AddRange(allLocations);
                    break;
                default:
                    break;
            }
        }
        return locationsInArea.OrderBy(l => l.Aisle).ThenBy(l => l.Column).ThenBy(l => l.Row);
    }
}

public class Area
{
    private string areaName;
    private string areaDescription;
    private readonly IList<Location> _locationsInArea;
    private List<AreaParameter> _areaParameters;

    private Area(string areaName, string areaDescription)
    {
        this.areaName = areaName;
        this.areaDescription = areaDescription;
        _locationsInArea = new List<Location>();
    }

    internal static Area CreateArea(string areaName, string areaDescription, List<AreaParameter> areaParameters,
                                    IEnumerable<Location> locationsInArea)
    {
        var area = new Area(areaName, areaDescription);
        area.AddAreaParameters(areaParameters);
        area.AddLocationsToArea(locationsInArea);
        return area;
    }

    private void AddLocationsToArea(IEnumerable<Location> locationsInArea)
    {
        foreach (var location in locationsInArea)
        {
            if (_locationsInArea.Any(l => l.LocationID == location.LocationID))
            {
                continue;
            }
            _locationsInArea.Add(location);
        }
    }

    private void AddAreaParameters(List<AreaParameter> areaParameters)
    {
        _areaParameters = areaParameters;
    }
}

public interface ILocationRepository
{
    Location GetLocationByCode(string LocationCode);
    IEnumerable<Location> GetLocations(Func<Location, bool> predicate);
}

public class LocationRepository : ILocationRepository
{
    private readonly List<Location> _locations;

    public LocationRepository()
    {
        _locations = new List<Location>
            {
                new Location {LocationCode = "A001A", LocationID = 1, Aisle = 1, Column = 1, Row = 1},
                new Location {LocationCode = "A001B", LocationID = 2, Aisle = 1, Column = 1, Row = 2},
                new Location {LocationCode = "A001C", LocationID = 3, Aisle = 1, Column = 1, Row = 3},
                new Location {LocationCode = "A002A", LocationID = 4, Aisle = 1, Column = 2, Row = 1},
                new Location {LocationCode = "A002B", LocationID = 5, Aisle = 1, Column = 2, Row = 2},
                new Location {LocationCode = "A002C", LocationID = 6, Aisle = 1, Column = 2, Row = 3},
                new Location {LocationCode = "A003A", LocationID = 7, Aisle = 1, Column = 3, Row = 1},
                new Location {LocationCode = "A003B", LocationID = 8, Aisle = 1, Column = 3, Row = 2},
                new Location {LocationCode = "A003C", LocationID = 9, Aisle = 1, Column = 3, Row = 3},
                new Location {LocationCode = "A004A", LocationID = 10, Aisle = 1, Column = 4, Row = 1},
                new Location {LocationCode = "A004B", LocationID = 11, Aisle = 1, Column = 4, Row = 2},
                new Location {LocationCode = "A004C", LocationID = 12, Aisle = 1, Column = 4, Row = 3},
                new Location {LocationCode = "B001A", LocationID = 13, Aisle = 2, Column = 1, Row = 1},
                new Location {LocationCode = "B001B", LocationID = 14, Aisle = 2, Column = 1, Row = 2},
                new Location {LocationCode = "B001C", LocationID = 15, Aisle = 2, Column = 1, Row = 3},
            };
    }

    public Location GetLocationByCode(string LocationCode)
    {
        return _locations.FirstOrDefault(l => l.LocationCode == LocationCode);
    }

    public IEnumerable<Location> GetLocations(Func<Location, bool> predicate)
    {
        return _locations.Where(l => predicate(l));
    }
}
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

I do like what you've done here. I'm a big fan of immutable objects and interface-based programming (for dependency injection, mocking via unit tests, etc.), so I've done a little squidging to make those happen as per below:

        // consumer code
        var locationRepository = new LocationRepository();

        // areaGenerator is a domain service which is used to calculate locations between a from and to location
        var areaGenerator = new AreaGenerator(locationRepository);
        var areaParameters = new AreaParameterList
        {
            { "A001A", "A002B", AreaDirection.Column },
            { "A001A", "A003C", AreaDirection.Row }
        };
        var locations = areaGenerator.CalculateLocationsInArea(areaParameters);
        var area = Area.CreateArea("Test Area", "Special Area", areaParameters, locations);

        Console.WriteLine("Area Name: " + area.AreaName);
        Console.WriteLine("Area Description: " + area.AreaDescription);
        Console.WriteLine("Area Parameters:");
        foreach (var parameter in area.AreaParameters)
        {
            Console.WriteLine("\tStart Location: " + parameter.StartLocation);
            Console.WriteLine("\tEnd Location: " + parameter.EndLocation);
            Console.WriteLine("\tDirection: " + parameter.AreaDirection);
            Console.WriteLine();
        }

        Console.WriteLine("Locations in Area:");
        foreach (var location in area.LocationsInArea)
        {
            Console.WriteLine("\tLocation Code: " + location.LocationCode);
            Console.WriteLine("\tLocation ID: " + location.LocationID);
            Console.WriteLine("\tAisle: " + location.Aisle);
            Console.WriteLine("\tColumn: " + location.Column);
            Console.WriteLine("\tRow: " + location.Row);
            Console.WriteLine();
        }

        Console.ReadLine();

public enum AreaDirection
{
    Column,

    Row,

    ALL
}

public interface ILocation
{
    string LocationCode { get; }

    int LocationID { get; }

    int Aisle { get; }

    int Column { get; }

    int Row { get; }

    string SortKey { get; }
}

public sealed class Location : ILocation
{
    private readonly string locationCode;

    private readonly int locationId;

    private readonly int aisle;

    private readonly int column;

    private readonly int row;

    public Location(string locationCode, int locationId, int aisle, int column, int row)
    {
        this.locationCode = locationCode;
        this.locationId = locationId;
        this.aisle = aisle;
        this.column = column;
        this.row = row;
    }

    public string LocationCode
    {
        get
        {
            return this.locationCode;
        }
    }

    public int LocationID
    {
        get
        {
            return this.locationId;
        }
    }

    public int Aisle
    {
        get
        {
            return this.aisle;
        }
    }

    public int Column
    {
        get
        {
            return this.column;
        }
    }

    public int Row
    {
        get
        {
            return this.row;
        }
    }

    public string SortKey
    {
        get
        {
            return this.aisle + "," + this.column + "," + this.row;
        }
    }
}

public interface ILocationList : IList<ILocation>
{
    void Add(string locationCode, int locationId, int aisle, int column, int row);
}

public sealed class LocationList : List<ILocation>, ILocationList
{
    public void Add(string locationCode, int locationId, int aisle, int column, int row)
    {
        this.Add(new Location(locationCode, locationId, aisle, column, row));
    }
}

public interface IAreaParameter
{
    string StartLocation { get; }

    string EndLocation { get; }

    AreaDirection AreaDirection { get; }
}

public sealed class AreaParameter : IAreaParameter
{
    private readonly string startLocation;

    private readonly string endLocation;

    private readonly AreaDirection areaDirection;

    public AreaParameter(string fromLocation, string toLocation, AreaDirection areaDirection)
    {
        this.startLocation = fromLocation;
        this.endLocation = toLocation;
        this.areaDirection = areaDirection;
    }

    public string StartLocation
    {
        get
        {
            return this.startLocation;
        }
    }

    public string EndLocation
    {
        get
        {
            return this.endLocation;
        }
    }

    public AreaDirection AreaDirection
    {
        get
        {
            return this.areaDirection;
        }
    }
}

public interface IAreaParameterList : IList<IAreaParameter>
{
    void Add(string fromLocation, string toLocation, AreaDirection areaDirection);
}

public sealed class AreaParameterList : List<IAreaParameter>, IAreaParameterList
{
    public void Add(string fromLocation, string toLocation, AreaDirection areaDirection)
    {
        this.Add(new AreaParameter(fromLocation, toLocation, areaDirection));
    }
}

public sealed class AreaGenerator
{
    private readonly ILocationRepository locationRepository;

    public AreaGenerator(ILocationRepository locationRepository)
    {
        this.locationRepository = locationRepository;
    }

    internal IEnumerable<ILocation> CalculateLocationsInArea(IAreaParameterList areaParameters)
    {
        var locationsInArea = new LocationList();

        foreach (var areaDetail in areaParameters)
        {
            var startLocation = this.locationRepository.GetLocationByCode(areaDetail.StartLocation);
            var endLocation = this.locationRepository.GetLocationByCode(areaDetail.EndLocation);
            var startAisle = startLocation.Aisle;
            var startColumn = startLocation.Column;
            var startRow = startLocation.Row;
            var endAisle = endLocation.Aisle;
            var endColumn = endLocation.Column;
            var endRow = endLocation.Row;

            switch (areaDetail.AreaDirection)
            {
                case AreaDirection.Row:
                    if (startAisle == endAisle)
                    {
                        Func<ILocation, bool> withinAisleAndColAndNotLastRow = l =>
                            ((l.Aisle == startAisle && l.Column >= startColumn && l.Row == startRow) &&
                             startRow != endRow)
                            ||
                            ((l.Aisle == startAisle && l.Column <= endColumn && l.Row == endRow) &&
                             startRow != endRow);
                        Func<ILocation, bool> withinAisleAndColAndIsLastRow = l =>
                            ((l.Aisle == startAisle && l.Column >= startColumn && l.Column <= endColumn &&
                              l.Row == startRow) && startRow == endRow)
                            ||
                            ((l.Aisle == startAisle && l.Column >= startColumn && l.Column <= endColumn &&
                              l.Row == endRow) && startRow == endRow);
                        Func<ILocation, bool> inBetweener = l =>
                            l.Aisle == startAisle && l.Row > startRow && l.Row < endRow;
                        var results = this.locationRepository.GetLocations(l =>
                            withinAisleAndColAndNotLastRow(l) || withinAisleAndColAndIsLastRow(l) ||
                            inBetweener(l));

                        locationsInArea.AddRange(results);
                    }
                    else
                    {
                        Func<ILocation, bool> withinAisleAndColAndIsLastRow = l =>
                            (l.Aisle == startAisle && l.Column >= startColumn && l.Row == startRow)
                                 || (l.Aisle == endAisle && l.Column <= endColumn && l.Row == endRow);
                        Func<ILocation, bool> withinAisleAndColAndNotLastRow = l =>
                            ((l.Aisle == startAisle && l.Row > startRow) || (l.Aisle == endAisle && l.Row < endRow));
                        Func<ILocation, bool> inBetweener = l => l.Aisle > startAisle && l.Aisle < endAisle;
                        var results = this.locationRepository.GetLocations(l =>
                            withinAisleAndColAndNotLastRow(l) || withinAisleAndColAndIsLastRow(l) ||
                            inBetweener(l));

                        locationsInArea.AddRange(results);
                    }

                    break;
                case AreaDirection.Column:
                    Func<ILocation, bool> predicate1 = l =>
                        ((l.Aisle == startAisle && l.Row >= startRow && l.Column == startColumn)
                            || (l.Aisle > startAisle && l.Aisle < endAisle && l.Column == startColumn));
                    Func<ILocation, bool> predicate2 = l =>
                        ((l.Aisle >= startAisle & l.Aisle <= endAisle &&
                          l.Column > startColumn & l.Column < endColumn)
                         || (l.Aisle == endAisle && l.Row <= endRow && l.Column == endColumn));
                    var locations = this.locationRepository.GetLocations(l => predicate1(l) || predicate2(l));

                    locationsInArea.AddRange(locations);
                    break;
                case AreaDirection.ALL:
                    var allLocations =
                        this.locationRepository.GetLocations(
                            l =>
                            l.SortKey.CompareTo(startLocation.SortKey) >= 0 &&
                            l.SortKey.CompareTo(endLocation.SortKey) <= 0);
                    locationsInArea.AddRange(allLocations);
                    break;
            }
        }

        return locationsInArea.OrderBy(l => l.Aisle).ThenBy(l => l.Column).ThenBy(l => l.Row);
    }
}

public interface IArea
{
    string AreaName { get; }

    string AreaDescription { get; }

    IAreaParameterList AreaParameters { get; }

    IEnumerable<ILocation> LocationsInArea { get; }
}

public sealed class Area : IArea
{
    private readonly string areaName;

    private readonly string areaDescription;

    private readonly IAreaParameterList areaParameters;

    private readonly ILocationList locationsInArea = new LocationList();

    private Area(string areaName, string areaDescription, IAreaParameterList areaParameters)
    {
        this.areaName = areaName;
        this.areaDescription = areaDescription;
        this.areaParameters = areaParameters;
    }

    public string AreaName
    {
        get
        {
            return this.areaName;
        }
    }

    public string AreaDescription
    {
        get
        {
            return this.areaDescription;
        }
    }

    public IAreaParameterList AreaParameters
    {
        get
        {
            return this.areaParameters;
        }
    }

    public IEnumerable<ILocation> LocationsInArea
    {
        get
        {
            return this.locationsInArea;
        }
    }

    internal static IArea CreateArea(
        string areaName,
        string areaDescription,
        IAreaParameterList areaParameters,
        IEnumerable<ILocation> locationsInArea)
    {
        var area = new Area(areaName, areaDescription, areaParameters);

        area.AddLocationsToArea(locationsInArea);
        return area;
    }

    private void AddLocationsToArea(IEnumerable<ILocation> newLocationsInArea)
    {
        foreach (var location in newLocationsInArea.Where(location => this.locationsInArea.All(l => l.LocationID != location.LocationID)))
        {
            this.locationsInArea.Add(location);
        }
    }
}

public interface ILocationRepository
{
    ILocation GetLocationByCode(string LocationCode);

    IEnumerable<ILocation> GetLocations(Func<ILocation, bool> predicate);
}

public sealed class LocationRepository : ILocationRepository
{
    private static readonly ILocationList locations = new LocationList
    {
        { "A001A", 1, 1, 1, 1 },
        { "A001B", 2, 1, 1, 2 },
        { "A001C", 3, 1, 1, 3 },
        { "A002A", 4, 1, 2, 1 },
        { "A002B", 5, 1, 2, 2 },
        { "A002C", 6, 1, 2, 3 },
        { "A003A", 7, 1, 3, 1 },
        { "A003B", 8, 1, 3, 2 },
        { "A003C", 9, 1, 3, 3 },
        { "A004A", 10, 1, 4, 1 },
        { "A004B", 11, 1, 4, 2 },
        { "A004C", 12, 1, 4, 3 },
        { "B001A", 13, 2, 1, 1 },
        { "B001B", 14, 2, 1, 2 },
        { "B001C", 15, 2, 1, 3 }
    };

    public ILocation GetLocationByCode(string LocationCode)
    {
        return locations.FirstOrDefault(l => l.LocationCode == LocationCode);
    }

    public IEnumerable<ILocation> GetLocations(Func<ILocation, bool> predicate)
    {
        return locations.Where(predicate);
    }
}
\$\endgroup\$
0

Your Answer

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

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