Sunday, May 7, 2017

Building an Achievement engine with Azure Service Fabric - 02

Previous Achievement Post

So here is the basic plan of what is needed to get going.  Like all plans this may change as we get going and learn more.  The general components that are needed are:

A Website to display a users view of the achievements.
This will allow a user to view the available achievements, the achievements they have earned, and hopefully the progress towards specific achievements.

A console application simulating activity that will generate achievements.
This console application will simulate the activity that will trigger achievement progression.  Ideally there would be another service that would watch a service bus for these events, since that is outside the scope of this blog series this console application will take the place of that service.

An API used to interact with the achievement engine.
This will be used by multiple components.  The website will use the API to get information about a users achievements.  A console application will use the API to trigger achievement events.

Actors for the individual achievement types.
This is where things get interesting.  Each achievement type (Daily Goal, Daily Fuel, Total Fuel, Streaks, and Date based) will have its own actor.  Each user will have their own instance of each of the achievement actors.  For example, given two separate users the following instances of the achievement actors would be instantiated.

User1:DailyGoal User2:DailyGoal
User1:DailyFuel User2:DailyFuel
User1:TotalFuel User2:TotalFuel
User1:Streaks User2:Streaks
User1:DateBased User2:DateBased

Actor for each user.
This actor will maintain the state of the achievements for the user.  This will stop the need to query each of the achievement types for the status of that specific achievement.


Why Actors?
The reason to use actors will make more sense as we write the code but for more information please go here:  Reliable Actors Introduction.  Pay close attention to the following sections:

  • Actor Lifetime
  • Distribution and Failover
  • Concurrency

The concurrency section is the most important one to check out.  The volume of events that could pass through this achievement engine could easily create a huge concurrency issue in the system.  The actor pattern solves this for the most part.

Another nice thing about using the actor pattern here is the actor itself is very specific.  For example when we build the Streaks achievement actor we will only need to concentrate on what that specific actor needs to do.

Monday, May 1, 2017

Building an Achievement engine with Azure Service Fabric - 01

First if you don't know what Azure Service Fabric is, go here to learn more.  I have been playing around with Azure Service Fabric and I felt that the virtual actor pattern used in Azure Service Fabric would be a good fit for building an achievement system.  For this series I will be basing the achievement engine off of the Nike Fuel trophies system.  I will not be implementing all the trophies just the ones I have grouped and referenced at the end of this post.

What is Nike Fuel?
Nike fuel is a proprietary unit of measurement for tracking fitness activity developed by Nike.  The algorithm is the same for everyone, so everyone is on an even playing field.

How to get Nike Fuel?
All products under the brand allow the user to earn Nike Fuel. Products include the Nike+ FuelBand accelerometer wristband; other wristbands such as SportWatch and SportBand; etc...

Goals
Most of the products that generate Nike Fuel allow the user to set a goal.  Example; reach 5,000 Nike Fuel in a day.

What is Going Green?
Going green means you completed your goal.  Example; earning 5,000 Nike Fuel in a single day would complete the goal referenced above.

Nike Fuel Trophies (Achievements)
It was difficult to get accurate information about the different trophies provided by Nike.  That being said I found several sources on the internet not affiliated with Nike that referenced information about the different Nike trophies that are available.  The trophy information below while it may not be 100% accurate is good enough to use as an example for this blog series.

Daily Goal Trophies

Water Beat Daily goal by 50%
Ice Double your daily goal
Fire Beat daily goal by 150%
Rainbow Triple your daily goal
Supernova Quadruple your daily goal

Daily Fuel Trophies

8k day Earn 8,000 fuel in a day
10k day Earn 10,000 fuel in a day
15k day Earn 15,000 fuel in a day
20k day Earn 20,000 fuel in a day

Total Fuel Trophies

5k Earned 5k fuel total
10k Earned 10k fuel total
25k Earned 25k fuel total
50k Earned 50k fuel total
75k Earned 75k fuel total
100k Earned 100k fuel total
150k Earned 150k fuel total
200k Earned 200k fuel total
300k Earned 300k fuel total
400k Earned 400k fuel total
500k Earned 500k fuel total
600k Earned 600k fuel total
700k Earned 700k fuel total
800k Earned 800k fuel total
900k Earned 900k fuel total
1M Earned 1M fuel total
1.15m Earned 1.15M fuel total
1.2M Earned 1.2M fuel total
1.5M Earned 1.5M fuel total
2M Earned 2M fuel total

Streak Trophies

7 days Get to green 7 days in a row
14 days Get to green 14 days in a row
30 days Get to green 30 days in a row
50 days Get to green 50 days in a row
100 days Get to green 100 days in a row
365 days Get to green 365 days in a row

Date Based Trophies

Jack O Lantern Get to green on Halloween
Uncle Sam Get to green on the 4th of July
Chocolate Heart Get to green on Valentine's Day
La Fiesta Get to green on Cinco de Mayo
Resolution Get to green on New Years Day

The plan for code
As part of this series I will be sharing the code in my GitHub account.  I am planning on making a branch for each blog post.  This way you will be able to follow along from post to post.  Also you should be able to access new branches before the corresponding blog post as I am writing the code over the coming months.

Next Achievement Post

Wednesday, December 21, 2016

Tuple Dictionary Key ???

The other day myself and some coworkers where going over some ideas on refactoring a process.  In this conversation the idea of a dictionary key of a tuple came up.  I thought this has to work especially after my last post about dictionaries => Fun with Dictionaries – Replacing ugly switch and if statements

So below is some code showing my findings.  Also here is the link to my github with all the code (https://github.com/devdaves/FunWithDictionaries2)

Here is an example factory where the dictionary with a tuple key is instantiated and used.

Using the Tuple as a key you can actually take some extremely complicated logic with multiple options and turn it into a simple dictionary lookup.

Tuesday, November 1, 2016

Building objects for your Unit Tests

When creating unit tests I prefer to use the AAA pattern (Arrange, Act and Assert).  During the Arrange part you usually end up instantiating objects to use for the test.  Depending on the complexity of the objects you are creating this can get to be a little bit of code.  Lets look at the code below:

Note the method BuildContact.  It takes a parameter of an Action<Contact> which would allow you to pass in a lambda expression to further define the properties you want to change on the Contact object for each unit test individually.  This centralizes the logic needed to create the Contact which is helpful but another benefit is what if there was a default value used by every test.  Look at this version of the BuildContact method:

Note the defaults are before the invoking of the action.  This way if a test wanted to override the default values it can.  Normally I create a separate test class for each of the methods I am writing unit tests for.  Using this method above to create the item I am using allows me to create the object with many defaults already set.It wouldn’t be unusual to have several BuildContact methods in different test classes each specific to the needs of that class (method) specifically.

Friday, October 28, 2016

Fun with Dictionaries – Replacing ugly switch and if statements

Recently I have been using a new technique to replace those ugly if statements and switch statements with a dictionary.  Lets get started with some code first:

So that Build method with all the if statements isn’t very appealing.  Lets make it better by using a switch statement instead:

That is better but I have started to prefer using a dictionary instead.  I think this code looks better:

Adding new items is one line now instead of several with the if and switch statements.

Tuesday, December 1, 2015

Singleton Azure WebJob

I have an Azure website project I have been working on.  This project required a scheduled task to run so I decided to give Azure WebJobs a try.  Creation of the job and getting it to deploy was pretty straight forward.  One of the things that is nice is for each instance your website uses the WebJob is duplicated.  For some WebJobs this can be be a really nice feature. 

The WebJob I am using does some data import and manipulation.  Having multiple instances of this WebJob running is not what I wanted.  After some searching I found that you can make your WebJob run on a single instance no matter how many instances your website is using.

To do this you will need to do the following.

  1. Add a “settings.job” file to the root of your WebJob project
  2. Modify the properties of the file and set the:
    • Build Action = Content
    • Copy to Output Directory = Copy always
  3. The contents of the “settings.job” file should contain the following json:
    { "is_singleton": true }

This should be all that is needed to get your WebJob running as a single instance when installed on many instances.

Saturday, May 16, 2015

Setting up Web API 2 with Windsor using OWIN hosted in IIS


tldr; Get the code from my Github repository here: https://github.com/devdaves/ExampleWebApi

Start a new ASP.NET Web Application

image

Select the Empty template.  Don't check any of the core references (Web Forms, MVC or Web API).

image

Once the project is ready to go, open the package manager console and run the following commands to install the necessary nuget packages.

  • install-package Microsoft.AspNet.WebApi.Owin
  • install-package Microsoft.Owin.Host.SystemWeb
  • install-package Castle.Windsor

Open the web.config and add the following code:

<appSettings>
  <add key="owin:AutomaticAppStartup" value="true" />
</appSettings>

In the Solution Explorer create a Startup class at the root of the site with the following code:

public class Startup
{
    public void Configuration(IAppBuilder appBuilder)
    {
        HttpConfiguration config = new HttpConfiguration();
        config.MapHttpAttributeRoutes();
        appBuilder.UseWebApi(config);
    }
}

In the Solution Explorer create a Models folder and inside the Models folder create a Contact class with the following code:

public class Contact
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

In the Solution Explorer create a Controllers folder and inside the Controllers folder create a Web API controller called ContactsController with the following code:

public class ContactsController : ApiController
{
    [HttpGet]
    [Route("contacts")]
    public IHttpActionResult Get()
    {
        return Ok(new Contact(){FirstName = "Donald", LastName = "Duck"});
    }
}

At this point you have enough to run the application and should be able to go to http://localhost:####/contacts in your browser and see the contact returned.

Now its time to add Windsor.  In the Solution Explorer add a new folder called Windsor.  Create a new class called WindsorDependencyScope with the following code:

internal class WindsorDependencyScope : IDependencyScope
{
    private readonly IWindsorContainer _container;
    private readonly IDisposable _scope;

    public WindsorDependencyScope(IWindsorContainer container)
    {
        if (container == null)
        {
            throw new ArgumentNullException("container");
        }

        _container = container;
        _scope = container.BeginScope();
    }

    public object GetService(Type t)
    {
        return _container.Kernel.HasComponent(t)
        ? _container.Resolve(t) : null;
    }

    public IEnumerable<object> GetServices(Type t)
    {
        return _container.ResolveAll(t)
        .Cast<object>().ToArray();
    }

    public void Dispose()
    {
        _scope.Dispose();
    }
}

Add a new class to the Windsor folder called WindsorHttpDependencyResolver with the following code:

internal sealed class WindsorHttpDependencyResolver : IDependencyResolver
{
    private readonly IWindsorContainer _container;

    public WindsorHttpDependencyResolver(IWindsorContainer container)
    {
        if (container == null)
        {
            throw new ArgumentNullException("container");
        }

        _container = container;
    }

    public object GetService(Type t)
    {
        return _container.Kernel.HasComponent(t)
            ? _container.Resolve(t) : null;
    }

    public IEnumerable<object> GetServices(Type t)
    {
        return _container.ResolveAll(t)
        .Cast<object>().ToArray();
    }

    public IDependencyScope BeginScope()
    {
        return new WindsorDependencyScope(_container);
    }

    public void Dispose()
    {
    }
}

In the Windsor folder create a new folder called Installers.  Create a new class in the Installers folder called ControllerInstaller with the following code:

public class ControllerInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly()
        .Pick().If(t => t.Name.EndsWith("Controller"))
        .Configure(configurer => configurer.Named(configurer.Implementation.Name))
        .LifestylePerWebRequest());
    }
}

Create another new class in the Windsor/Installers folder called DefaultInstaller with the following code:

public class DefaultInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly()
            .Pick()
            .WithServiceDefaultInterfaces()
            .Configure(c => c.LifestyleTransient()));
    }
}

Now we need to make the final changes to the Startup class we created in the root of the site.  Make it look like this:

public class Startup
{
    public void Configuration(IAppBuilder appBuilder)
    {
        var container = new WindsorContainer().Install(
            new ControllerInstaller(),
            new DefaultInstaller());
        var httpDependencyResolver = new WindsorHttpDependencyResolver(container);

        HttpConfiguration config = new HttpConfiguration();
        config.MapHttpAttributeRoutes();
        config.DependencyResolver = httpDependencyResolver;
        appBuilder.UseWebApi(config);
    }
}

Now that Windsor is setup lets add a contact repository and add that as a dependency to the controller so we can see Windsor in action.

In the Solution Explorer add a folder called Repository.  In the Repository folder add a new interface called IContactsRepository with the following code:

public interface IContactsRepository
{
    Contact GetContact();
}

Add a class called ContactsRepository to the Repository folder with the following code:

public class ContactsRepository : IContactsRepository
{
    public Contact GetContact()
    {
        return new Contact(){FirstName = "Donald", LastName = "Duck"};
    }
}

Now lets go add the dependency to the ContactsController, make the ContactsController code look like this:

public class ContactsController : ApiController
{
    private IContactsRepository repository;

    public ContactsController(IContactsRepository repository)
    {
        this.repository = repository;
    }

    [HttpGet]
    [Route("contacts")]
    public IHttpActionResult Get()
    {
        return Ok(this.repository.GetContact());
    }
}

That should be it.  Running the site now everything should work.