Design, Test and Develop like it's heaven on Earth - Part 17

Published on 10/24/2019

Over the course of the next few posts I want to work towards being able to replace my old blog site. There’s quite a bit to do, like cleaning up a lot of the Blazor template stuff that we don’t need, adding support for images and migrating my old blog posts into the new database. But first, let’s start by building and showing a list of blog posts.

Iterating on Blogs.razor

Previously we created this Blazor page and simply added a hardcoded list to it. This list we need to load from the database, so in this post we’re going to dive all the way down the stack and back up. My starting point? I want to make sure I understand what I need to display here. This is where I want to iterate quickly over some simple UI changes. Using the UITest solution config allows me to switch out to the service adaptor mock. Next, I need to provide some sort of model that the page will know about. Perhaps just a title, and we need to link to the blog post too. These are the two elements we already have on the hardcoded page.

public class BlogItemView
{
    public string Key { get; set; }
    public string Title { get; set; }
}

How is it going to get this data? In a similar way that we load a specific blog post. We add quite a bit infrastructure to the Blazor page and include a new method on the adaptor’s interface.

@page "/blogs"
@using helloserve.com.Adaptors
@using helloserve.com.Models
@inject IBlogServiceAdaptor ServiceAdaptor

<h1>Blogs</h1>

@if (_blogItems == null)
{
    <p><em>Loading...</em></p>
}
else
{
<ul>
    @foreach (var item in _blogItems)
    {
        <li>
            <NavLink class="nav-link" href="@AsUrl(item.Key)">
                @item.Title
            </NavLink>
        </li>
    }
</ul>
}

@functions {
    private IEnumerable<BlogItemView> _blogItems;

    protected override async Task OnInitAsync()
    {
        _blogItems = await ServiceAdaptor.ReadAll();
    }

    private string AsUrl(string key)
    {
        return $"blogs/{key}";
    }
}
public interface IBlogServiceAdaptor
{
    Task<BlogView> Read(string title);
    Task<IEnumerable<BlogItemView>> ReadAll();
}

This is a bit of a rewrite of the page. I changed the markup to add the <li> tags within a loop of a collection of the items, but the structure of the resulting markup is exactly the same as before. I also included the injection of the same service adaptor, and added that override on the OnInit() event handler to load a collection of the model. Now all we need to is to implement that ReadAll() method on the mock adaptor.

public async Task<IEnumerable<BlogItemView>> ReadAll()
{
    return await Task.FromResult(new List<BlogItemView>()
    {
        new BlogItemView() { Title = "This is a first title", Key = "this_is_a_first_title" },
        new BlogItemView() { Title = "This is a second title", Key = "this_is_a_second_title" },
        new BlogItemView() { Title = "Here is another entry!!", Key = "here_is_another_entry" },
        new BlogItemView() { Title = "What different types of heading variations are there?", Key = "what_different_types_of_heading_variations_are_there" }
    });
}

And of course, I’m not going to get await without having to fulfill the interface on the real adaptor, but we leave it as code gen put it there. When we run we see the following list:

list of bulleted blogs

That’s pretty cool, but perhaps it shouldn’t be bulleted? And perhaps it should include a few more details, like the published date perhaps. Also, the repetition of the title “Blog” from the sidebar to the main area seem overdone. I’ll add a published date property to the item model, and cleanup the markup a bit more and add two of my own CSS classes to better control the size and padding.

public class BlogItemView
{
    public string Key { get; set; }
    public string Title { get; set; }
    public DateTime PublishDate { get; set; }
}
<div>
    @foreach (var item in _blogItems)
    {
    <p>
        <div>
            <NavLink class="nav-link" href="@AsUrl(item.Key)">
                <div class="heading">@item.Title</div>
            </NavLink>
            <div class="heading-notation">@item.PublishDate.ToShortDateString()</div>
        </div>
    </p>
    }
</div>

Now it looks like this

list of blogs with date

Going Top Down

Now that we have an index of blog posts, we can move on to sourcing it from the database. As usual our starting point is a unit test, and since code completion created that ReadAll() method for us on the real adaptor implementation already, let’s start with that one. We’re now following the “top down” approach to adding a feature into the system, meaning we’re starting at the top layer (refer to the architecture diagram from part 2).

[TestMethod]
public async Task ReadAll_Verify()
{
    //arrange
    List<Blog> blogs = new List<Blog>()
    {
        new Blog(),
        new Blog(),
        new Blog()
    };
    _serviceMock.Setup(x => x.ReadAll())
        .ReturnsAsync(blogs);

    //act
    IEnumerable<BlogItemView> result = await _adaptor.ReadAll();

    //assert
    Assert.AreEqual(3, result.Count());
}

Crossing the layer boundaries

We’re all ready setting up the service mock to return some blog entries, so it requires that we create that method on the IBlogService abstraction as well. It’s important to notice here that the service mock doesn’t return that BlogItemView model directly. There is only one model that this abstraction can know about, and that is the Blog model in the domain. Remember, the abstraction can only know about cross-cutting concerns. We have to do this manually, and it’s typical when you have to cross the boundaries of your architecture. So in this instance we manually change the abstraction to include that method, and then use the code completion tool again to generate the default implementation on the real BlogService class.

The problem with tools

This is where productivity tools start to become a problem. Let’s pretend for a moment that I had attempted to assign it to IEnumerable<BlogItemView> result directly. If I use the code completion in Visual Studio in such a case, it will actually attempt to add all the required references to IBlogService in order to satisfy our demand that it should return a model that is declared in the web project.

no definition

It will add the complete namespace, and it will add a project reference from the helloserve.com.Domain.Abstractions project to the helloserve.com.Web project too. After this is completed, you will start to see circular reference errors when trying to compile the solution. Other tools like ReSharper will do the same thing. For inexperienced developers that rely too heavily on these productivity tools this sort of thing can become really hard to understand and unravel once it’s all gone wrong. And this is why it is so important for all team members to understand the architecture of the design, and how the tools should be used in order to live by that architecture. Unfortunately the tools are dumb and will only follow your instructions. I often hear “Because ReSharper suggested it...” when doing code reviews. When a lack of experience and understanding is compounded by a misuse of tools, really bad things start to happen.

Completing the implementation

Of course our unit test doesn’t pass. This is as per process, so we supply an implementation for the real adaptor.

public async Task<IEnumerable<BlogItemView>> ReadAll()
{
    var items = await _service.ReadAll();
    return Config.Mapper.Map<IEnumerable<BlogItemView>>(items);
}

We can’t return items directly here of course, so let’s call into the mapper to do that conversion for us. Remember, this is one of the primary roles of an adaptor - to map from the domain models to the view or database models and visa versa. This code compiles, but when we run the unit test it gives out a long error complaining that no mapper has been defined that understands how to map Blog to BlogItemView. We need to add this to the configuration.

public BlogConfig()
{
    CreateMap<Blog, BlogItemView>();
    CreateMap<Blog, BlogView>()
        .ForMember(x => x.Content, opt => opt.MapFrom(blog => blog.Content.AsHtml()));
}

We don’t need to supply any overriding property mappers, so we don’t need to add an additional unit test. Now this test passes and we confirm that we’re correctly taking a collection of blogs from the domain service and converting it to some elements to view on a Blazor page.

Conclusion

In this post we completed the first bit of a new feature which is to list a collection of blog posts on an index page of sorts. We didn’t complete the entire feature, however. Instead we stopped at the boundary between the presentation layer and the domain layer, with the domain layer being just a default implementation. At this point you might want to submit your work for a code review already, it’s also ready to be handed over to the app developers (if it was an API) or the front-end team to complete their work with a concrete implementation in place. Depending on your team and your project it might also be fine to release it like this. Perhaps there are feature toggles in place, or perhaps it’s not a public API or site in which case you can more easily manage any one hitting an end point that isn’t functioning.

What about bottom up?

The other approach is of course, bottom up. Here you would start to build the feature from the database up through your layers until you reach the presentation layer. You won’t need to upward code-complete with default implementations, but your solution will (for a period of time at least) have a bunch of code that are not used or referenced. This could cause inconsistent results with code analysis tools for instance. The other problem with bottom up is that it is heavily dependant on having a full design. Sometimes working down means you can define what you need as you need it. Working up means you have to know what you need to provide beforehand.

Why this consideration?

In some cases it’s not possible to complete an entire feature in one sprint. And if it is possible, you probably want to complete smaller parts of the feature and submit it for code review and discussion in regular intervals rather than one big chunk of work from which multiple rework items might be identified at the end of the sprint or development period which will put you under pressure. Which approach you choose depends on a multitude of factors, but you should make sure that your design allows for you to make that choice. The boundary layers is the best place to divide up the work pieces, but even within a layer, depending on the complexity of the feature, it might be possible to break up the work and submit it piecemeal.