M-V-VM Building an ERP-system – part 1 of N – the model design

Skrevet - Wednesday, May 26th, 2010 kl. 23:15 | Kategori - * Coding, English posts

I’ve decided to post a series on how I would start out designing an ERP solution with focus on Model-View-ViewModel. To fully understand the reasoning, this first part mainly discusses the model in which I work. Many of the later choices should be clearer if you understand the reasoning behind it.

First of, there is no ‘one way’ to do M-V-VM – and how you use it, will determine, how (and if) you benefit from it. I design ERP-systems (Enterprise Resource Planning) which are typically rich on business-logic and have high demands for user-friendliness especially in dataentry screens. Thus, if I did UI-designing like I did back in the VBA-days, I would end up with a jumble of spaghetti-code with UI-concerns creeping into the cracks of my business-objects. So, the main focus for me is getting rid of all things not business in nature and isolating it in ViewModels. Also, the domain of ERP-systems is typically used by different clients – ie. webservices, smart-clients and EDI-interfaces and having to maintain each of these with the same business model is critical. Performance is another key-point as the system is so large that basically the entire organisation will use it – in differnet ways and in different areas – and I cannot allow one users actions to impact all others. Also, I use WPF as the front-end and WPF has a lot of functionality that lends well to M-V-VM.

To keep things simple, the domain I use in this series is much less complex, but will scale very well for very complex scenarios. We will have Customers, Orders and OrderLines as the main objects – the business model looks as follows:

public interface IAggregateRoot
{

long Id{get;}
bool IsValid(out string[] errors);
bool IsTransient{get;}

}
public interface ICustomer:IAggregateRoot
{

string AccountNumber{get;set;}
string CompanyName{get;set;}

}
public interface IOrder:IAggregateRoot
{

DateTime OrderDate{get;set;}
ICustomer Customer{get;set;}
IEnumerable OrderLines{get;}
void AddOrderLine(IOrderLine orderLine);
void ClearOrderLines();

}
public interface IOrderLine
{

decimal Quantity{get;}
string Description{get;}
decimal PriceEach{get;}

}

Okay, should be kinda simple, but there are a lot of design-choices already made. Below, I’ll outline some of the reasons behind them:

Why does IOrderLine not inherit from IAggregateRoot?
Order lines belong to an order – and must never be allowed to exist with-out an order. Also, there is no complex logic in an orderline – it could easily be a struct in essence. People at this point may be thinking “but… when it goes to the database, it should have an Id!” – I disagree. An Id would make sense if we are allowed to update the orderline outside the context of the order – I have effectively made sure that cannot happen (in the domain – the gloves are off if you have access to the database).
Building an ERP-system is all about keeping options limited to the ones that make sense business-wise. The simpler the domain – the simpler the maintainance.

Why are there no setters on IOrderLine – how will you ever correct an order line?
I won’t correct it. I’ll replace it. Basically, the orderline is treated as if it was a simple type eg. an integer. I could have an RemoveOrderLine method, in the IOrder interface, but it would not make sense here. If I were to do that, how would I know I was removing the correct one? There is no Id to tell me which one is which – and there is no logic telling me that I cannot have two identical order lines. I would have to make sure, I was referencing the correct orderline (in memory) before removing it – adding needless complexity. Also, when the user edits the order, I’ll update the orderlines in one fell swoop as I’ll show later.

Why does the ICustomer interface not have an IEnumerable of the Orders?
Because it does not need it (yet, anyways). If I want to find all orders for a specific customer, I could query orders instead of querying the customer. Again, simplicity in the model is key – and two-way connections is complexity (who will update the other – how do I make sure they are saved correctly… etc.). Avoiding deep object dependencies is also key – before you know of it, you’re loading half the database because you wanted this one customer…

Why use interfaces? Isn’t that needless complexity? How about YAGNI?
Yes, at this point, interfaces are overkill – I jumped the gun at this point (they will be needed later), but interfaces are much easier to discuss instead of jumbling around in implementations which may be littered with all kinds of implementation details.

Why the IAggregateRoot interface and the methods in it?
Basically the IAggregateRoot interface is mostly for my convinience. At this point it is not really needed, but I do not want to change the interfaces later on (the final solution is built…), so, there are a few methods that are a bit ahead of time. Also, adherring to DRY (Don’t Repeat Yourself) is one of the most important code practices in my book, so that will shine through in the solution.

In part 2, I’ll discuss the implementations of the above interfaces and the reasoning behind it.

About the series:
The solutions introduced here are not entry-level material. You should understand generics and basic patterns to fully utilize the material presented. The solution represents about 3 years of experience working with one ERP-solution. Some of the choices taken are heavily influenced from the large-scale solution and may seem overkill for the simple examples here, but the idea is to show the end-result of my experience and hopefully you as the reader will at least see a new way of doing things. The solution is built in .Net 4.0 with heavy use of WPF’s capabilities. Here you can find the source-code for the series. You are free to use this code for any purpose, but it is presented as-is with no guarantees. Enjoy!

Feed | Trackback |

1 Comment
  1. Goblincave » M-V-VM Building an ERP-system – part 2 of N – the model implementation said,

    June 6, 2010 at 22:22

    [...] discussed in part 1, we need to implement the model somehow. The solution-source code shows the refactored result. [...]

Post a Comment