patterncsharpMinor
Using commands as deferred behaviour
Viewed 0 times
commandsbehaviourdeferredusing
Problem
I was thinking of building a really flexible fluent API for my persistence layer. One of my goals is to achieve somehow the following result:
For this, I got a few ideas, the one I like the most is the use of Command objects to defer each action on the data source until a
This my implementation of a command object:
I would then have the following base persistence strategy:
```
/**
* Represents a persistence strategy that has deferred behaviour
**/
public abstract class BaseDeferredPersistenceStrategy : IDeferredPersistenceStrategy
where TSource : IPersistable
{
protected abstract ICommand> DeferredGetAll();
// DeferredWhere and DeferredSort methods receive the previous command
// because the implementation may reuse it.
// (For example, a filter condition
IEnumerable users = _userRepo.Use(users => users.Create(new User("MattDamon"),
new User("GeorgeClooney"),
new User("BradPitt"))
.Where(u => u.Username.Length > 8)
.SortAscending(u => u.Username));For this, I got a few ideas, the one I like the most is the use of Command objects to defer each action on the data source until a
Resolve() method is called.This my implementation of a command object:
/**
* Represents an action to be executed
**/
public interface ICommand
{
// The action's result (so anyone can inspect the output without executing it again)
TResult Result { get; }
// Executes the action
TResult Execute();
}
public class SimpleCommand : ICommand
{
// The action to execute
private Func _execution;
private TResult _result;
public TResult Result { get { return _result; } }
// Creates a new command that will execute the specified function
public SimpleCommand(Func executeFunction)
{
_execution = executeFunction;
}
public TResult Execute()
{
return (_result = _execution());
}
}I would then have the following base persistence strategy:
```
/**
* Represents a persistence strategy that has deferred behaviour
**/
public abstract class BaseDeferredPersistenceStrategy : IDeferredPersistenceStrategy
where TSource : IPersistable
{
protected abstract ICommand> DeferredGetAll();
// DeferredWhere and DeferredSort methods receive the previous command
// because the implementation may reuse it.
// (For example, a filter condition
Solution
What are your functional and non functional requirements here?
It is really uncommon to do not know your storage in advance for sure. Developing new universal data access technology as a part of something else is a way to big troubles.
Generally speaking, it is common to use some data access technology directly for simple CRUD tasks like reporting, or isolate data access technology by repositories.
-
CRUD with direct access - it is flexible and cheap, but we have dependency on data access technology.
-
Repositories - there are two kind of them: generic and custom.
-
Generic is basically an exposure of IQueryable or your own query language. It almost always a very bad thing to have. You can not really isolate yourself from data technology specifics, it is a constant obstacle on using advanced features of data access technology and it is a really, really big leak of abstraction. It is almost useless to say the least in many situations.
-
Custom repositories look good even for some CRUD, you definitely need them for DDD scenarios. It is the only way to isolate dependencies on concrete data access technology. They look like a concrete and simple contract for data access layer with potentially replaceable implementation.
Custom repository might be something like this:
You have some flexibility with these Query objects, but be aware of over-complicating them as it makes testing and implementation much more complex. It is better to define more queries if necessary to be absolutely explicit about their contract. It usually gives a lot if you operate in terms of Models or DTO instead of data relational mapper objects, especially for Entity Framework.
You might find it attractive to have some asymmetry - use reader/writers to change db state and direct access to ORM for UI data fetching/reporting where flexibility is welcomed.
Hope this is relevant somehow. BTW, there are a lot of good books on this.
It is really uncommon to do not know your storage in advance for sure. Developing new universal data access technology as a part of something else is a way to big troubles.
Generally speaking, it is common to use some data access technology directly for simple CRUD tasks like reporting, or isolate data access technology by repositories.
-
CRUD with direct access - it is flexible and cheap, but we have dependency on data access technology.
-
Repositories - there are two kind of them: generic and custom.
-
Generic is basically an exposure of IQueryable or your own query language. It almost always a very bad thing to have. You can not really isolate yourself from data technology specifics, it is a constant obstacle on using advanced features of data access technology and it is a really, really big leak of abstraction. It is almost useless to say the least in many situations.
-
Custom repositories look good even for some CRUD, you definitely need them for DDD scenarios. It is the only way to isolate dependencies on concrete data access technology. They look like a concrete and simple contract for data access layer with potentially replaceable implementation.
Custom repository might be something like this:
interface IUserReader
{
User Read(int id);
IPage Read(UserQuery query);
}
class UserQuery : PagingQuery
{
public int? CompanyId { get; set; }
}
class PagingQuery
{
public int Page { get; set; } = 1;
public int PageSize { get; set; } = 20;
}
interface IPage : IEnumerable
{
int Page { get; }
int PageCount { get; }
}You have some flexibility with these Query objects, but be aware of over-complicating them as it makes testing and implementation much more complex. It is better to define more queries if necessary to be absolutely explicit about their contract. It usually gives a lot if you operate in terms of Models or DTO instead of data relational mapper objects, especially for Entity Framework.
You might find it attractive to have some asymmetry - use reader/writers to change db state and direct access to ORM for UI data fetching/reporting where flexibility is welcomed.
Hope this is relevant somehow. BTW, there are a lot of good books on this.
Code Snippets
interface IUserReader
{
User Read(int id);
IPage<User> Read(UserQuery query);
}
class UserQuery : PagingQuery
{
public int? CompanyId { get; set; }
}
class PagingQuery
{
public int Page { get; set; } = 1;
public int PageSize { get; set; } = 20;
}
interface IPage<T> : IEnumerable<T>
{
int Page { get; }
int PageCount { get; }
}Context
StackExchange Code Review Q#113519, answer score: 2
Revisions (0)
No revisions yet.