A reference architecture (part 8)

…continued from part 7

The query side

So now the command side works, but what about the query side? As you know, queries bypass the application and domain logic; they just query a data source for information directly and expose it in the desired format. This means that those service operations don’t use the request handler dispatcher to dispatch requests to the correct handler, but instead use the repositories directly.

To clearly separate commands from queries, it’s probably cleaner to split commands and queries in two separate services: a command service and a query service; especialliy if both sides use their own database; but in our case – for simplicity – we will put them all in the same service.

As mentioned earlier we need two queries: FindAllBlogEntries and FindBlogEntryById. As these query operations don’t use the application layer, we have to define their dto’s in the service layer; so add a class library project called ArchitectureExample.Service.Dtos to the Service Layer solution folder, and put it physically in the \Trunk\Sources\ folder:

p8_1

Then add the following classes to this project:

[DataContract]
public class BlogEntryDetails
{
    public BlogEntryDetails() { }
    public BlogEntryDetails(int blogEntryId, string title, string body)
    {
        BlogEntryId = blogEntryId;
        Title = title;
        Body = body;
    }

    [DataMember]
    public int BlogEntryId { get; set; }

    [DataMember]
    public string Title { get; set; }

    [DataMember]
    public string Body { get; set; }

    [DataMember]
    public List<CommentDetails> Comments { get; set; }
}


[DataContract]
public class BlogEntrySummary
{
    public BlogEntrySummary() {}
    public BlogEntrySummary(int blogEntryId, string title, string body)
    {
        BlogEntryId = blogEntryId;
        Title = title;
        Body = body;
    }

    [DataMember]
    public int BlogEntryId { get; set; }

    [DataMember]
    public string Title { get; set; }

    [DataMember]
    public string Body { get; set; }
}


[DataContract]
public class CommentDetails
{
    public CommentDetails() {}
    public CommentDetails(int commentId, string name, string emailAddress, string commentText)
    {
        CommentId = commentId;
        Name = name;
        EmailAddress = emailAddress;
        CommentText = commentText;
    }

    [DataMember]
    public int CommentId { get; set; }

    [DataMember]
    public string Name { get; set; }

    [DataMember]
    public string EmailAddress { get; set; }

    [DataMember]
    public string CommentText { get; set; }
}


[DataContract]
public class FindAllBlogEntriesResponse
{
    public FindAllBlogEntriesResponse() {}
    public FindAllBlogEntriesResponse(List<BlogEntrySummary> blogEntrySummaries)
    {
        BlogEntrySummaries = blogEntrySummaries;
    }

    [DataMember]
    public List<BlogEntrySummary> BlogEntrySummaries { get; set; }
}

[DataContract]
public class FindBlogEntryDetailsRequest
{
    public FindBlogEntryDetailsRequest() {}
    public FindBlogEntryDetailsRequest(int blogEntryId)
    {
        BlogEntryId = blogEntryId;
    }

    [DataMember]
    public int BlogEntryId { get; set; }
}


[DataContract]
public class FindBlogEntryDetailsResponse
{
    public FindBlogEntryDetailsResponse() {}
    public FindBlogEntryDetailsResponse(BlogEntryDetails blogEntryDetails)
    {
        BlogEntryDetails = blogEntryDetails;
    }

    [DataMember]
    public BlogEntryDetails BlogEntryDetails { get; set; }
}

 

Then add the service operations to the IArchitectureExampleService interface of the ArchitectureExample.Service.Contracts project:

[OperationContract]
FindAllBlogEntriesResponse FindAllBlogEntries();

[OperationContract]
FindBlogEntryDetailsResponse FindBlogEntryDetails(FindBlogEntryDetailsRequest request);

 

And implement them in the ArchitectureExampleService of the ArchitectureExample.Service.Adapter project:

public class ArchitectureExampleService : IArchitectureExampleService
{
    private readonly IHandlerDispatcher _handlerDispatcher;
    private readonly IBlogEntryRepository _blogEntryRepository;

    public ArchitectureExampleService(IHandlerDispatcher handlerDispatcher, IBlogEntryRepository blogEntryRepository)
    {
        _handlerDispatcher = handlerDispatcher;
        _blogEntryRepository = blogEntryRepository;
    }

    public FindAllBlogEntriesResponse FindAllBlogEntries()
    {
        return new FindAllBlogEntriesResponse(_blogEntryRepository.BlogEntries.ToList().Select(be => new BlogEntrySummary(be.Id, be.Title, be.Body)).ToList());
    }

    public FindBlogEntryDetailsResponse FindBlogEntryDetails(FindBlogEntryDetailsRequest request)
    {
        var blogEntryDetails = _blogEntryRepository.BlogEntries.SingleOrDefault(be => be.Id == request.BlogEntryId);
        return new FindBlogEntryDetailsResponse(blogEntryDetails!=null ? new BlogEntryDetails(blogEntryDetails.Id, blogEntryDetails.Title, blogEntryDetails.Body) : null);
    }
}

 

And also the service agent:

public class ArchitectureExampleServiceAgent : ServiceProxyBase<IArchitectureExampleService>, IArchitectureExampleService
{    
    public FindAllBlogEntriesResponse FindAllBlogEntries()
    {
        return Channel.FindAllBlogEntries();
    }

    public FindBlogEntryDetailsResponse FindBlogEntryDetails(FindBlogEntryDetailsRequest request)
    {
        return Channel.FindBlogEntryDetails(request);
    }
}

 

And finally add a test:

[TestMethod]
public void FindAllBlogEntriesTest()
{
    using (var architectureExampleServiceAgent = new ArchitectureExampleServiceAgent("BasicHttpBinding_IArchitectureExampleService"))
    {
        var response = architectureExampleServiceAgent.FindAllBlogEntries();

        Assert.IsTrue(response.BlogEntrySummaries.Count > 0);
    }
}

 

Next time we will implement a way to automatically throw business faults by the service if an exception occurs.

Advertisements

2 thoughts on “A reference architecture (part 8)

  1. Gerard Torres says:

    Ludwig,

    Where would you populate the Comments list in the BlogEntry entity? I couldn’t locate an example of this in the project.

    I’m thinking you only fill it when you need it, and that you would do so in the appropriate query method of the ArchitectureExampleService?

    For example if you needed to show the comments for a blog entry detail screen you would include these in FindBlogEntryDetailsResponse with something like

    var comments = _blogEntryRepository.Comments.Where(c => c.BlogEntryId == request.BlogEntryId);
    blogEntryDetails.Comments = comments;

    Does that seem correct or would this responsibility belong to another layer/component?

    Thanks

    • Ludwig Stuyck says:

      Yes, the whole point is to disable lazy loading and to retrieve only what you really. If you need the top 10 comments, you would explicitly query for them in your query handler.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: