Composing queries with commands

We will streamline the implementation while implementing commands in our repository. The following code will allow us to create an interface which enforces IncludeData to be set for all queries:

   public interface IQueryRoot
{
bool IncludeData { get; set; }
}

We will use IQueryRoot as a base interface for the query handler, which returns generic return typed data. The following code extends the IQueryRoot and provide two interfaces to support synchronous and asynchronous operations:

    public interface IQueryHandler<out TReturn> : IQueryRoot
{
TReturn Handle();
}
public interface IQueryHandlerAsync<TReturn> : IQueryRoot
{
Task<TReturn> HandleAsync();
}

Let's keep creating multiple generic interfaces required for different queries that inherit the query handler with a configured return type, and additionally define the fields required in each query type.

We will be using Variant Generic Interfaces. Even though they are out of scope, let's discuss them briefly to understand the code. The covariant generic interface expects the type to implement methods that have a return type specified in the generic type parameters using the out keyword. The contravariant generic interface expects the type to accept method arguments as the type specified in the generic type parameters using the in keyword.

We will be using the contravariant generic interface in the following example, whereas the covariant generic interfaces were used widely in this chapter:

    public interface IGetAllPostsQuery<in T> : 
IQueryHandler<IEnumerable<Post>>,
IQueryHandlerAsync<IEnumerable<Post>>
{
}
public interface IGetPaginatedPostByKeywordQuery<in T> :
IQueryHandler<IEnumerable<Post>>,
IQueryHandlerAsync<IEnumerable<Post>>
{
string Keyword { get; set; }
int PageNumber { get; set; }
int PageCount { get; set; }
}
public interface IGetPostByAuthorQuery<T> :
IQueryHandler<IEnumerable<Post>>,
IQueryHandlerAsync<IEnumerable<Post>>
{
string Author { get; set; }
}
public interface IGetPostByCategoryQuery<T> :
IQueryHandler<IEnumerable<Post>>,
IQueryHandlerAsync<IEnumerable<Post>>
{
string Category { get; set; }
}
public interface IGetPostByHighestVisitorsQuery<T> :
IQueryHandler<IEnumerable<Post>>,
IQueryHandlerAsync<IEnumerable<Post>>
{
}
public interface IGetPostByIdQuery<T> :
IQueryHandler<Post>, IQueryHandlerAsync<Post>
{
int? Id { get; set; }
}
public interface IGetPostByPublishedYearQuery<T> :
IQueryHandler<IEnumerable<Post>>,
IQueryHandlerAsync<IEnumerable<Post>>
{
int Year { get; set; }
}
public interface IGetPostByTitleQuery<T> :
IQueryHandler<IEnumerable<Post>>,
IQueryHandlerAsync<IEnumerable<Post>>
{
string Title { get; set; }
}

Let's create a command handler that would handle a command execution similar to the query handler interface that orchestrates the query execution. The following  command handler code will create a generic interface which provides handle mechanism with generic return type:

    public interface ICommandHandler<out TReturn>
{
TReturn Handle();
}
public interface ICommandHandlerAsync<TReturn>
{
Task<TReturn> HandleAsync();
}

Let's keep creating multiple generic interfaces required for different commands that inherit the command handlers. The following code will create interfaces required the Post CRUD operation using commands:

    public interface ICreatePostCommand<TReturn> : 
ICommandHandler<TReturn>, ICommandHandlerAsync<TReturn>
{
}
public interface IDeletePostCommand<TReturn> :
ICommandHandler<TReturn>, ICommandHandlerAsync<TReturn>
{
}
public interface IUpdatePostCommand<TReturn> :
ICommandHandler<TReturn>, ICommandHandlerAsync<TReturn>
{
}

We need a QueryBase, which allows database context to be injected through the constructor, and this will be used by all queries that inherit QueryBase. The following code will create QueryBase which consumes BlogContext used by all queries accessing the data context:

    public class QueryBase
{
internal readonly BlogContext Context;
public QueryBase(BlogContext context)
{
this.Context = context;
}
}

The GetAllPostsQuery inherits QueryBase and IGetAllPostsQuery, it enforces the developer to define IncludeData field and implement the Handle and HandleAsync methods that consume data context from the QueryBase type. The following code provides the concrete implementation of GetAllPostsQuery consuming QueryBase type:

    public class GetAllPostsQuery : QueryBase,
IGetAllPostsQuery<GetAllPostsQuery>
{
public GetAllPostsQuery(BlogContext context) : base(context)
{
}

public bool IncludeData { get; set; }
public IEnumerable<Post> Handle()
{
return IncludeData
? Context.Posts
.Include(p => p.Author).Include(p =>
p.Blog).Include(p => p.Category)
.ToList()
: Context.Posts
.ToList();
}
public async Task<IEnumerable<Post>> HandleAsync()
{
return IncludeData
? await Context.Posts
.Include(p => p.Author).Include(p =>
p.Blog).Include(p => p.Category)
.ToListAsync()
: await Context.Posts
.ToListAsync();
}
}

We could take up an assignment to implement the remaining queries we had earlier using the new approach.

Similar to QueryBase, CommandBase is a base required for all commands that expose the data context for all consumers. The following code will create CommandBase which consumes BlogContext consumed by all command objects:

    public class CommandBase
{
internal readonly BlogContext Context;
public CommandBase(BlogContext context)
{
Context = context;
}
}

The CreatePostCommand inherits CommandBase and ICreatePostCommand, it enforces developers to define all the fields necessary to create a Post object and implement the Handle and HandleAsync methods that consume the data context from the CommandBase type. The following code creates concrete implementation of CreatePostCommand consuming CommandBase type:

    public class CreatePostCommand : CommandBase, 
ICreatePostCommand<int>
{
public CreatePostCommand(BlogContext context) : base(context)
{
}
public string Title { get; set; }
public string Content { get; set; }
public string Summary { get; set; }
public int BlogId { get; set; }
public int AuthorId { get; set; }
public int CategoryId { get; set; }
public DateTime PublishedDateTime { get; set; }
public int Handle()
{
Context.Add(new Post
{
Title = Title,
Content = Content,
Summary = Summary,
BlogId = BlogId,
AuthorId = AuthorId,
CategoryId = CategoryId,
PublishedDateTime = PublishedDateTime,
CreatedAt = DateTime.Now,
CreatedBy = AuthorId,
Url = Title.Generate()
});
return Context.SaveChanges();
}
public async Task<int> HandleAsync()
{
Context.Add(new Post
{
Title = Title,
Content = Content,
Summary = Summary,
BlogId = BlogId,
AuthorId = AuthorId,
CategoryId = CategoryId,
PublishedDateTime = PublishedDateTime,
CreatedAt = DateTime.Now,
CreatedBy = AuthorId,
Url = Title.Generate()
});
return await Context.SaveChangesAsync();
}
}

We could take up an assignment to implement the Update and Delete commands and proceed with configuring the queries and commands in the new repository. The interface code required for the repository is listed below, which has provisions to query single or a list of data and also to execute any commands; every one of them is implemented in a generic fashion:

    public interface IPostRepositoryWithCommandsQueries
{
IEnumerable<Post> Get<T>(T query) where T :
IQueryHandler<IEnumerable<Post>>;
Task<IEnumerable<Post>> GetAsync<T>(T query) where T :
IQueryHandlerAsync<IEnumerable<Post>>;
Post GetSingle<T>(T query) where T : IQueryHandler<Post>;
Task<Post> GetSingleAsync<T>(T query)
where T : IQueryHandlerAsync<Post>;
int Execute<T>(T command) where T : ICommandHandler<int>;
Task<int> ExecuteAsync<T>(T command) where T :
ICommandHandlerAsync<int>;
}

The generic implementation of the new repository is listed as follows as well. At this point, the repository doesn't know which query or command it's executing as it's resolved at run-time, the following code would create concrete implementation of PostRepositoryWithCommandQueries consuming command handling statements as well:

     public class PostRepositoryWithCommandsQueries : 
IPostRepositoryWithCommandsQueries
{
private readonly BlogContext _context;
public PostRepositoryWithCommandsQueries(BlogContext context)
{
_context = context;
}
public IEnumerable<Post> Get<T>(T query)
where T : IQueryHandler<IEnumerable<Post>>
{
return query.Handle();
}
public async Task<IEnumerable<Post>> GetAsync<T>(T query)
where T : IQueryHandlerAsync<IEnumerable<Post>>
{
return await query.HandleAsync();
}
public Post GetSingle<T>(T query)
where T : IQueryHandler<Post>
{
return query.Handle();
}
public async Task<Post> GetSingleAsync<T>(T query)
where T : IQueryHandlerAsync<Post>
{
return await query.HandleAsync();
}
public int Execute<T>(T command) where T : ICommandHandler<int>
{
return command.Handle();
}
public async Task<int> ExecuteAsync<T>(T command) where T :
ICommandHandlerAsync<int>
{
return await command.HandleAsync();
}
}

The controller update to accommodate the new repository is listed as follows:

    public class PostsController : Controller
{
private readonly BlogContext _context;
private readonly IPostRepositoryWithCommandsQueries
_postRepositoryWithCommandsQueries;
public PostsController(BlogContext context,
IPostRepositoryWithCommandsQueries repositoryWithCommandsQueries)
{
_context = context;
_postRepositoryWithCommandsQueries = repositoryWithCommandsQueries;
}
}

The Index method that executes GetAllPostsQuery is updated, as follows:

    // GET: Posts
public async Task<IActionResult> Index()
{
return View(await _postRepositoryWithCommandsQueries.GetAsync(
new GetAllPostsQuery(_context)
{
IncludeData = true
}));
}

The Create command replaces the data context manipulation, as follows:

    public async Task<IActionResult> Create([Bind("Id,Title,
Content,Summary,PublishedDateTime,Url,VisitorCount,CreatedAt,
ModifiedAt,BlogId,AuthorId,CategoryId")] Post post)
{
// code removed for brevity
await _postRepositoryWithCommandsQueries.ExecuteAsync(
new CreatePostCommand(_context)
{
Title = post.Title,
Summary = post.Summary,
Content = post.Content,
PublishedDateTime = post.PublishedDateTime,
AuthorId = post.AuthorId,
BlogId = post.BlogId,
CategoryId = post.CategoryId
});
// code removed for brevity
}

We have covered exhaustively how commands are created and consumed along with queries in repositories and controllers. Let's visit the solution required for the previous assignment in the next section to proceed with the missing implementations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset