Products »
Safety Stock Calculator » Here
Design a custom inventory adapter in .NET
Intended audience: .NET developers
This document explains how to create a completely custom adapter that allows LSSC to load inventory and sales data from a 3rd party application.
Big picture
LSSC is simple BI (business intelligence) application that features inventory analysis based on the Lokad
forecasting technology. LSSC includes a generic framework to retrieve inventory data (i.e. sales, stock levels, suppliers, ...) from virtually any 3rd party applications (most usually ERP, eCommerce or accounting applications).
The elementary piece of code that connects to the 3rd party application in order to retrieve the inventory data is called an
inventory adapter in the LSSC terminology.
If the data of the 3rd party application can be accessed through SQL, then the most straightforward solution consists of using the
LSSC Editor, a GUI tool that let you easily design and validate the adapter that wraps the actual SQL queries.
Yet, there are situations where the data cannot be accessed through SQL. Such situations typically include Web Services and any kind of custom API. In such cases, a custom .NET adapter is required. This guide will detail how to implement such an adapter.
Getting the source code
LSSC is an open source project with the source code is kept in a Subversion repository. You need to check-out the code hosted at
Google Project (look for
trunk/dot-net-legacy).
Use
Microsoft Visual Studio 2008 to open the solution named
Lokad.SafetyStock.Windows.sln. Visual Studio will prompt you because the project files have been modified. It's OK, we have indeed customized
MsBuild project files, but it will not cause any harm. Just accept to load the projects normally.
There should be no particular dependencies, thus you should be able to compile the solution right away. Select
Lokad.SafetyStock.Windows as the
StartUp Project, hit F5 and LSSC should run.
Understanding how adapters work
The solution contains a dozen of C# projects. Yet, there are only two things that are relevant to the design of a custom inventory adapter:
Lokad.SafetyStock.InventoryAdapter C# project that contains the classes defining the framework for inventory adapters.- Special XML file format used to list inventory adapters being loaded by LSSC on start-up.
Basically, implementing a new inventory adapter consists of inheriting two classes
Lokad.SafetyStock.IInventoryAdapterLokad.SafetyStock.IInventoryAdapterFactory
This design is nothing but a simple
Factory design pattern. The factory is responsible for instantiating the adapter - usually by passing some settings such as a connection string for example.
When LSSC gets started, all factories (i.e. inheritors of
IInventoryAdapterFactory) listed in
Adapters.ladx file from the project directory and any other
*.ladx files from this directory or
My Documents\My Lokad Extensions directory get loaded into the default instance of
InventoryAdapterFactoryCollection. The factories are used to instantiate on-demand new
IInventoryAdapter instances.
Then, once an
IInventoryAdapter is instantiated, it can be used to retrieve inventory data that typically includes sales data, stock levels, suppliers ...
For a simple adapter implementation, we suggest to have a look at the
TsvInventoryAdapter.cs code file in
Lokad.SafetyStock.InventoryAdapter project. This implementation loads inventory from a TSV (tab separated value) file with a simple syntax. Although,
don't reuse this TSV adapter as base implementation for your own adapter because this adapter is not a
real implementation in many ways (implementation is not scalable, retrieved data is supposed immutable, ...).
This guide is
not a substitute for reference documentation. The latest documentation of all those classes is available at
build.lokad.com/doc/lssc. We suggest to refer to this reference documentation when actually inheriting classes.
Setting up a dedicated project
Implementing your custom inventory adapters requires introducing two classes. Although you could obviously add those class files to the
Lokad.SafetyStock.InventoryAdapter project, we suggest not to touch any existing project because it would complicate the maintenance the day you want to upgrade LSSC to its latest version.
Instead, we suggest to add a new
C# Library project to the solution. This project will contain your classes. Add
Lokad.SafetyStock.InventoryAdapter as a project reference.
We suggest to create up-front two classes
MyInventoryAdapterFactory that inherits InventoryAdapterFactoryBase.MyInventoryAdapter that inherits InventoryAdapterBase.
You can use the Visual Studio 2008 refactoring tools to auto-generate mock implementations of those two classes. In particular, it will auto-generate the methods
GetSkuNames and
GetOrders for the
InventoryAdapterBase implementation; those methods will be further detailed in the following.
Closer look at InventoryAdapterFactoryBase
The first class to be implemented is the inheritor of
IInventoryAdapterFactory. In practice we suggest to inherit
InventoryAdapterBase which provides handy default behaviors. The factory contains a very limited amount of logic, thus coding the factory is actually quite simple.
public abstract class InventoryAdapterFactoryBase
{
IInventoryAdapterDefinition Definition { get; }
IInventoryAdapter GetInventoryAdapter(IInventoryAdapterSettings settings);
}
The
Definition property is nothing but descriptive meta-data (such as display name, adapter description, ...) that are exposed to the user while setting up a new report in LSSC. We suggest to create a private class
MyDefinition, located within your
InventoryAdapterFactoryBase class, that inherits
IInventoryAdapterDefinition. If all the meta-data are static (which is usually the case), you can make
MyDefinition a
singleton.
The method
GetInventoryAdapter is responsible for instantiating the adapter instances. The
settings argument usually contains the credentials used to connect to the 3rd party data source. At this point, we suggest to return a new instance of your un-implemented adapter calling the default constructor.
Referencing your adapter in LSSC
Now that you have setup a minimal project for your custom inventory adapter, we are going to
reference this adapter in LSSC. This should facilitate the testing of the adapter by directly launching LSSC.
Note: the IInventoryAdapterFactory.Definition must be implemented in order not to crash LSSC when the list of available factories will be displayed.
Step 1: Open extensions folder by clicking "Tools | Locate Extensions Folder" from the LSSC Menu (the folder will be created if it does not exist).
Step 2: Copy your compiled factory dll to that folder. Let's say it was named
MyLibrary.dll.
Step 3: Create
MyLSSCAdapters.ladx file and open it with your favorite XML editor. You can use Notepad or
Notepad++ which is a free editor with XML editing capabilities.
Note: LADX file is a simple header file with custom XML format. It lists adapters that should be loaded by LSSC when it starts up. You can have as many LADX files as you want. LSSC will automatically find and load all files from the Extensions folder.
Step 4: Add following XML to the empty file and save it:
<?xml version="1.0" encoding="utf-8"?>
<lokad
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<lssc-adapters>
<adapter
id="MyFirstAdapter"
factory="MyLibrary.MyCustomAdapterFactory, MyLibrary"
factory-assembly="MyLibrary.dll" />
</lssc-adapters>
</lokad>Tip: Lokad Safety Stock Calculator includes a lot of custom adapters in its distribution. They can serve as a reference, should you want to use some advanced features like optional parameter passing. Just look for the xml\Adapters.ladx in Lokad.SafetyStock.InventoryAdapter project.
Step 5: Restart the LSSC application.
Your new adaper should now be visible within the adapters list. Should there be any errors or warnings while loading this adapter, they will be displayed in the separate window.
Optional Step 6: in order to redistribute LSSC adapters you need to copy
LADX file and DLL files that it references to the
Extensions folder on another machine.
Wizard tips and other remarks
During the LSSC setup will be asked for your Lokad credentials, yet you do not need a Lokad account to actually test your inventory adapter. Thus, we suggest you enter mock credentials (anything will do), just to move on the next wizard panels. Then, for the report settings, you can just leave the default settings.
After completing the wizard, we suggest that you
save your report, using
File»Save. It will save you the trouble of re-setting up your report each time.
Finally, the inventory retrieval can be performed through
Tools»Partial Refresh»Retrieve Inventory. If errors happen, then you can use
View»Event Logs to get the full detail of the exception that has been thrown.
Closer look at InventoryAdapterBase
The second class to be implemented is the inheritor of
IInventoryAdapter. In practice, we suggest to inherit
InventoryAdapterBase (as opposed to inheriting
IInventoryAdapter directly) as this class provides handy default behaviors.
public abstract InventoryAdapterBase
{
public abstract SkuName[] GetSkuNames();
public abstract SkuOrder[] GetOrders(
int index, int pageSize, DateTime startTime, DateTime endTime);
// ...
}
There are only two methods marked as
abstract:
GetSkuNames and
GetOrders.
GetSkuNames
The method
GetSkuNames is straightforward. SKU stands for
Stock Keeping Unit. Check the
SKU definition if you are not familiar with this concept. Each SKU has an
identifier (
SkuName.Identifier), which will be uploaded to Lokad and a name (
SkuName.Name), which will be actually displayed to the user. Optionally, you can use the same value for SKU identifier and SKU name.
Still,
there is a privacy catch with
GetSkuNames. The
SkuName class comes with only two properties:
public class SkuName : SkuInfo<string>
{
public string Identifier { get; } // uploaded to Lokad
public string Name { get; } // NOT uploaded to Lokad
}
Although those two properties
Identifier and
Name have similar roles, there is a major difference between the two: only the
Identifier gets uploaded to Lokad. This pattern is a required in order to enforce the basic
obfuscation policy of Lokad. Thus, we strongly suggest to use some anonymous values for the
Identifiers. Typically, database primary keys are perfect for that since they do not carry any information about the item or the SKU itself.
Then, there is yet another reason not to use non-anonymous data (typically product names) for the
Identifier values: the string tokens must be compatible with the naming rules enforced by the Lokad Web Services for time-series names. Indeed, as detailed on the
TimeSeries.asmx API:
Naming: time-series and forecasting tasks have names (i.e. strings used as identifiers). Those names could not include more than 64 characters. Additionally, the following list of characters ;
< > \ / " ' are prohibited.
Finally, please note that, like all the other
IInventoryAdapter.GetFoo() methods,
GetSkuNames is expected
to return a sorted array. The expected sort order is the one associated to
SkuIdHolder (the base class of
SkuInfo).
GetOrders
The method
GetOrders is slightly more complicated. Basically, this method returns
demand orders i.e. anything that actually decreases the inventory levels (it can be
sales orders but also
internal inventory consumption for example). The
SkuOrder is very simple class:
public class SkuOrder : SkuIdHolder
{
// public string Identifier { get; } - inherited from SkuIdHolder
public DateTime OrderDate { get; }
public double Quantity { get; }
}
The reason why
GetOrder takes argument is simple:
performance. The number of orders can be very large, and it might be very unpractical to try to retrieve all the data at once. Thus,
GetOrders supports, through its argument, two behaviors
- data paging that indicates the segment of data to be returned.
- date constraints that enable incremental data retrieval.
The
GetOrders takes a
startTime argument. While designing your inventory adapter, you may wonder how does the value of this argument gets
chosen by LSSC. It's actually quite simple:
- for the first retrieval for the report,
startTime is set as 1901-01-01 which will retrieve, hopefully, even the oldest orders. - for later retrievals,
startTime is equal to the last past period date, as retrieved during the previous retrieval operation.
This scheme ensures that the inventory data gets incrementally retrieved.
For developers (yet not for users), this behavior can be a bit surprising, because if you happen to create
old orders after the first retrieval, then those orders are likely to be ignored by a later
Retrieve operation simply because LSSC assume that such orders do not exist in the first place. Indeed, in a real situation, those orders would have been already retrieved.
Optional methods
The abstract class
InventoryAdapterBase contains several methods that can be
optionally implemented. Indeed, not all 3rd party applications are born equal, and certain 3rd party application may not support lead times or suppliers for example. In such a situation, the end user will still be able to manually enter the data into LSSC. Yet, it is advised to implement as many methods as possible since it will significantly save time for the end user.
public abstract class InventoryAdapterBase
{
public virtual bool IsProductNameAvailable { get; }
public virtual SkuProductName[] GetProductNames();
public virtual bool IsStockOnHandAvailable { get; }
public virtual SkuQuantity[] GetStocksOnHand();
public virtual bool IsSupplierAvailable { get; }
public virtual SkuSupplier[] GetSuppliers();
// ...
}
Those methods
GetFoo are all associated with an
IsFooAvailable property. The default implementation in
InventoryAdapterBase returns
false for all those properties. If you provide an implementation for
GetProductNames, you must override
IsProductNameAvailable as well (returning
true obviously), otherwise your implementation will simply be ignored by LSSC.
Unit testing your adapter implementation
It is usually considered a good practice to validate your code through
unit testing. The LSSC project comes with its own testing suite that relies on
NUnit.
LSSC includes a
reusable set of unit tests targeting inventory adapters. This means that you don't need to actually
implement any unit-test, you just need to reference your new inventory adapter to be tested against the default set of unit tests provided by LSSC.
We suggest that you create a new Class library project named
MyInventoryAdapter.Tests. This assembly will need to reference a couple of libraries including
- Your own custom library
MyInventoryAdapter nunit.framework.dll, located in /libLokad.SafetyStock.InventoryAdapterLokad.SafetyStock.InventoryAdapter.Tests
In this new project, you need to add a single class
MyInventoryAdapterTests that inherits
InventoryAdapterCommonTests. See the implementation of
TsvInventoryAdapterTests pasted below as an example.
using NUnit.Framework;
using Lokad.SafetyStock;
using Lokad.SafetyStock.Tests;
[TestFixture]
public class TsvInventoryAdapterTests : InventoryAdapterCommonTests
{
public TsvInventoryAdapterTests()
: base(GetInventoryAdapter(),
new DateTime(2005, 01, 01), // start date provided for GetOrders
new DateTime(2009, 01, 01)) // end date provided for GetOrders
{
}
// Helper method to create a default instance of the adapter.
public static InventoryAdapterBase GetInventoryAdapter()
{
// The string passed below as argument is the connection string.
return new TsvInventoryAdapter(null,
new InventoryAdapterSettings(@"..\..\MockTsvInventoryAdapterData.txt"));
}
}
Finally, use NUnit to execute the testing suite contained in your
MyInventoryAdapter.Tests library.