Our software architecture enjoys the benefits of ORM
technology.
These patterns show how you might structure your code.
Entities
are the classes
that represent the functional domain model.
In this architecture, we aim to keep the Entity
classes
just data and free of logic. The Entities
in this architecture have properties of simple types and references or lists to other Entities
.
class Supplier
{
int ID { get; set; }
string Name { get; set; }
Address Address { get; set; }
IList<Product> Products { get; set; }
}
You might even want to avoid enums
in the Entity
classes
and put those in the business layer instead. Often the database contain enum
-like Entities
, which you could as Entities
in your model. This to keep it a purer representaton of the data model:
class Supplier
{
Industry Industry { get; set; }
}
class Industry
{
int ID { get; set; }
int Name { get; set; }
}
enum IndustryEnum
{
Retail = 1,
Travel = 2
}
Creating collections upon initialization is recommended for Entity
classes
. NHibernate
does not always create the collections for us. By creating a collection we can omit some null
checks in the code:
class Supplier
{
var Products { get; set; } = new List<Product>();
}
For Entity
classes
, public
members should be virtual
, otherwise persistence technologies may not work. This is because ORM's
want to create Proxy classes
, that tend to override all the properties.
class Supplier
{
virtual int ID { get; set; }
virtual int Name { get; set; }
...
}
Generally avoid inheritance within your Entity
models, because it can make using data technologies harder.
The previous code examples for Entities
were just illustrative pseudo-code. This might be a more realistic example:
public class Supplier
{
public virtual int ID { get; set; }
public virtual string Name { get; set; }
public virtual Address Address { get; set; }
public virtual IList<Product> Products { get; set; } = new List<Product>();
}
Mappings
are classes
programmed for a particular persistence technology, e.g. NHibernate
, that map the Entity
model to how the objects
are stored in the data store (e.g. an SQL Server
database). A Mapping
defines which class
maps to which table
and which column
maps to which property.
DTO
= data transfer object. DTO's
only contain data, no logic. They are used to transfer data between different parts of a system. In certain situations, where passing an Entity
is not handy or efficient, a DTO
might be a good alternative.
For instance: A specialized, optimized SQL
query may return a result with a particular record structure. You could program a DTO
that is a strongly typed version of these records. In many cases you want to query for Entity
objects
instead, but in some cases this is not fast / efficient enough and you might resort to a DTO
.
DTO's
can be used for other data transfers than SQL
queries as well.
A Repository
is like a set of queries. Repositories
return or save Entities
in the data store. Simple types, not Entities
, are preferred for parameters. The Repository
pattern is a way to put queries in a single place. The Repositories'
job is also to provide an optimal set of queries.
Typically, every Entity type
gets its own Repository
.
It might be best to not expose types from the underlying persistence technology, so the Repository
abstraction stays neutral.
Any Repository type
will get an associated Repository interface
. This keeps our system loosely coupled from the underlying persistence technology.
The Repository interfaces
are also handy for testing, to create a fake in-memory data store, instead of connecting to a real database. The API
JJ.Framework.Data
can help abstract this data access, providing a base for these Repositories
and interfaces
.