a teal trianglea yellow circle
Published on

Building a Beautiful OKR with AntBlazor - Part 2

Authors

Planning the Backend

There is one objective with many key results. I added some properties that I think will be useful. These may change as we figure out the UI/UX a bit more.

Some feature ideas:

  • Objectives should be toggleable if they are active or not
  • Objectives should have a start and end date
  • Objectives should have a brief title, and a longer description
  • Key Results should have a brief title, and a longer description
  • Key Results should be toggleable if they are active or not
  • Key Results should have a completion percent 0%-100%
  • Key Results should have a priority (higher the number the more important)
public class Objective
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public DateTime DueDate { get; set; }
    public DateTime StartDate { get; set; }
    public bool Active { get; set; }
}
public class KeyResult
{
    public Guid Id { get; set; }
    public Guid ObjectiveId { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public bool Active { get; set; }
    public decimal Completion { get; set; }
    public int Priority { get; set; }
}

That's it for the core models! Lets add it to the project...

Coding it

In the OKR.Core project, create a folder called Models - you guessed it, for our domain models. Now add the two models from above in files: Objective.cs and KeyResult.cs

Repositories

Now we have the core models, we need to build an interface that abstracts data access. Lets build two repositories for the Objectives and Key Results.

Create another folder in OKR.Core called Repositories.

Data access code, especially in repositories can get pretty repetitive, so lets abstract it to a generic repository:

IGenericRepository.cs

using System.Collections.Generic;
using System.Threading.Tasks;

namespace OKR.Core.Repositories
{
    public interface IGenericRepository<T>
    {
        public Task AddAsync(T model);
        public Task UpdateAsync(T model);
        public Task DeleteAsync(T model);

        public Task<IReadOnlyList<T>> GetAllAsync();
        public Task<IReadOnlyList<T>> GetPagedAsync(int page = 0);
    }
}

This code provides the simple CRUD operations on any model T

Now lets build the specific repositories for OKRs:

IObjectiveRepository.cs

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using OKR.Core.Models;

namespace OKR.Core.Repositories
{
    public interface IObjectiveRepository : IGenericRepository<Objective>
    {
        Task<IReadOnlyList<Objective>> GetAllActiveAsync(bool active = true);
        Task<IReadOnlyList<Objective>> GetAllWithinDueDateAsync();
        Task<Objective> GetByIdAsync(Guid id);
    }
}

Note the added functions that are specific to Objective properties

IKeyResultRepository.cs

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using OKR.Core.Models;

namespace OKR.Core.Repositories
{
    public interface IKeyResultRepository : IGenericRepository<KeyResult>
    {
        Task<IReadOnlyList<KeyResult>> GetAllByObjectiveAsync(Guid objectiveId);
    }
}

Pretty simple here, I don't think we need to provide any filtering on the data access level here, since there won't be any ridiculous number of key results inside an objective

Mocking the Repositories

Before we get started with the frontend we have to mock the repositories to get some 'real' data.

Lets create a new class library project to hold the mock repositories called OKR.Core.Mock. Add a project reference to OKR.Core. Create a folder called Repositories and create the files: MockObjectiveRepository.cs and MockKeyResultRepository.cs - implementing the underlying interface. You should be able to autogenerate the functions like so:

public class MockObjectiveRepository : IObjectiveRepository
    {
        public async Task AddAsync(Objective model)
        {
            throw new NotImplementedException();
        }

        public async Task UpdateAsync(Objective model)
        {
            throw new NotImplementedException();
        }
        
        ...

At this point, lets just make a couple functions return mock data, the rest can throw exceptions:

MockKeyResultRepository.cs

public async Task<IReadOnlyList<KeyResult>> GetAllByObjectiveAsync(Guid objectiveId) => new[]
{
    new KeyResult
    {
        Id = Guid.NewGuid(),
        Active = true,
        Completion = 0,
        Description = "Do something...",
        Title = "Something",
        Priority = 1,
        ObjectiveId = objectiveId
    },
    new KeyResult
    {
        Id = Guid.NewGuid(),
        Active = true,
        Completion = (decimal) 0.35,
        Description = "Do this...",
        Title = "This",
        Priority = 4,
        ObjectiveId = objectiveId
    }
    ,
    new KeyResult
    {
        Id = Guid.NewGuid(),
        Active = true,
        Completion = (decimal) 0.80,
        Description = "Do that...",
        Title = "This",
        Priority = 10,
        ObjectiveId = objectiveId
    }
};

MockObjectiveRepository.cs

public async Task<IReadOnlyList<Objective>> GetAllAsync() => new[]
{
    new Objective
    {
        Active = true,
        Title = "Q1 Growth",
        Description = "Grow in Q1 by doing some productive things.",
        Id = Guid.NewGuid(),
        StartDate = DateTime.Now.Subtract(TimeSpan.FromDays(4)),
        DueDate = DateTime.Now.AddDays(5)
    },
    new Objective
    {
        Active = true,
        Title = "Lorem Ipsum",
        Description = "A sentence describing what this is",
        Id = Guid.NewGuid(),
        StartDate = DateTime.Now.Subtract(TimeSpan.FromDays(4)),
        DueDate = DateTime.Now.AddDays(5)
    },
    new Objective
    {
        Active = true,
        Title = "Q2 Growth",
        Description = "Grow in Q2 by doing some productive things.",
        Id = Guid.NewGuid(),
        StartDate = DateTime.Now.Subtract(TimeSpan.FromDays(20)),
        DueDate = DateTime.Now.AddDays(5)
    },
    new Objective
    {
        Active = true,
        Title = "Q3 Growth",
        Description = "Grow in Q3 by doing some productive things.",
        Id = Guid.NewGuid(),
        StartDate = DateTime.Now.Subtract(TimeSpan.FromDays(50)),
        DueDate = DateTime.Now.AddDays(100)
    },
    new Objective
    {
        Active = false,
        Title = "Q4 Growth",
        Description = "Grow in Q4 by doing some productive things.",
        Id = Guid.NewGuid(),
        StartDate = DateTime.Now.AddDays(100),
        DueDate = DateTime.Now.AddDays(200)
    },
};

Now we have some mock repositories return some data.


In the next post, I begin the UI with AntBlazor, using the mock data.

← Part 1 | Part 3 →