Usage - GetcuReone/FactFactory GitHub Wiki

How to use the fact factory

How to use a fact factory will be easiest to show with an example.

I must say right away that the example code can be found here.

Let's imagine that we have a movie service. In our case, we will work only with 3 databases:

  • Users DB;
  • Movies DB;
  • Discounts DB.

Let's get started with an example.

Part 1. Project creation

There is nothing special here. You can create any project. I will create a test project for simplicity and give it the name of it MovieServiceExample.

After that, we need to add a link to our library. You can add it as a NuGet package or download and compile the code.

Part 2. Environment preparation

Now that we have a project, let's create our service in it (of course, not all, but only a small part). The first thing we need to do is add entities users, movies and discounts.

public class User
{
    public int Id { get; set; }

    public string Name { get; set; }

    public string Email { get; set; }
}

public class Movie
{
    public int Id { get; set; }

    public string Name { get; set; }

    public int Cost { get; set; }
}

public class Discount
{
    public int Id { get; set; }

    public int UserId { get; set; }

    public int MovieId { get; set; }

    public int MovieDiscount { get; set; }
}

Let's create our database semblance in test class.

List<User> UserDB;

List<Movie> MovieDB;

List<Discount> DiscountDB;

[TestInitialize]
public void Initialize()
{
    UserDB = new List<User>
    {
        new User { Id = 1, Email = "[email protected]", Name = "John Cornero"},
        new User { Id = 2, Email = "[email protected]", Name = "Liza Famez"},
        new User { Id = 3, Email = "[email protected]", Name = "Judy Gonzalez"},
    };

    MovieDB = new List<Movie>
    {
        new Movie { Id = 1, Name = "My Hero Academia: Heroes Rising", Cost = 11, },
        new Movie { Id = 2, Name = "Bad Boys for Life", Cost = 12, },
        new Movie { Id = 3, Name = "Bloodshot", Cost = 13, },
    };

    DiscountDB = new List<Discount>
    {
        new Discount { Id = 1, MovieDiscount = 5, MovieId = 1, UserId = 3 },
        new Discount { Id = 2, MovieDiscount = 4, MovieId = 2, UserId = 2 },
        new Discount { Id = 3, MovieDiscount = 3, MovieId = 3, UserId = 1 },
    };
}

Fine. Now we have not only a project, but even data to work with :)

Part 3. Preparing facts

Unfortunately, the factory cannot work with your classes as they are. They need a shell, which we call facts. In order not to waste time implementing the methods of each fact, we will use what the fact factory offers out of the box. Now let's create the facts.

/// <summary>
/// Fact stores user information
/// </summary>
public class UserFact : FactBase<User>
{
    public UserFact(User value) : base(value)
    {
    }
}

/// <summary>
/// Fact stores user email information
/// </summary>
public class UserEmailFact : FactBase<string>
{
    public UserEmailFact(string value) : base(value)
    {
    }
}

/// <summary>
/// The fact stores information about the size of the discount
/// </summary>
public class MovieDiscountFact : FactBase<int>
{
    public MovieDiscountFact(int value) : base(value)
    {
    }
}

/// <summary>
/// Fact stores movie id information
/// </summary>
public class MovieIdFact : FactBase<int>
{
    public MovieIdFact(int value) : base(value)
    {
    }
}

/// <summary>
/// The fact stores information about the movie
/// </summary>
public class MovieFact : FactBase<Movie>
{
    public MovieFact(Movie value) : base(value)
    {
    }
}

/// <summary>
/// The fact stores information about the cost of buying a movie for the user
/// </summary>
public class MoviePurchasePriceFact : FactBase<int>
{
    public MoviePurchasePriceFact(int value) : base(value)
    {
    }
}

Part 4. Preparing rules for calculating facts

Now that we have the facts, we can create rules to calculate them.

FactRuleCollection Rules;

[TestInitialize]
public void Initialize()
{
    //... previous code

    Rules = new FactRuleCollection
    {
        // If we have a user, then we can find out his email.
        (UserFact fact) => new UserEmailFact(fact.Value.Email),

        // If we have an email, then we can recognize the user.
        (UserEmailFact fact) => new UserFact(UserDB.Single(user => user.Email == fact.Value)),

        // If we have a movie ID, you can find a movie.
        (MovieIdFact fact) => new MovieFact(MovieDB.Single(movie => movie.Id == fact.Value)),

        // If we have a movie, you can find a movie ID.
        (MovieFact fact) => new MovieIdFact(fact.Value.Id),

        // If we have a user and a movie, then we can add a user discount amount.
        (UserFact userFact, MovieFact movieFact) =>
        {
            int result = 0;

            var dicounts = DiscountDB.Where(dicount => dicount.UserId == userFact.Value.Id && dicount.MovieId == movieFact.Value.Id).ToList();

            if (dicounts != null)
            {
                foreach(var dicount in dicounts)
                {
                    result += dicount.MovieDiscount;
                }

                if (result > movieFact.Value.Cost)
                    result = movieFact.Value.Cost;
            }

            return new MovieDiscountFact(result);
        },

        // If we have a movie and a discount size, then we can calculate the cost of the movie.
        (MovieFact movieFact, MovieDiscountFact movieDiscountFact) => new MoviePurchasePriceFact(movieFact.Value.Cost - movieDiscountFact.Value),
    };
}

Part 5. Create fact factory

Not much left :) Now we can safely create our fact factory.

FactFactory Factory;

[TestInitialize]
public void Initialize()
{
    //... previous code

    Factory = new FactFactory();
    Factory.Rules.AddRange(Rules);
}

Part 6. Finally

Now let's use the created tool.

[TestMethod]
[Description("Calculate the cost of a 'My Hero Academia: Heroes Rising' movie for a 'Judy Gonzalez' user")]
public void CalculatingCostBuyingMovie_1()
{
    // We have information about the user's mail and the identifier of the film, what he wants to buy.
    string email = "[email protected]";
    int movieId = 1;

    // Let's tell the factory what we know
    var container = new FactContainer
    {
        new UserEmailFact(email),
        new MovieIdFact(movieId),
    };

    // We ask the factory to calculate the cost of buying a movie for our user.
    int price = Factory.DeriveFact<MoviePurchasePriceFact>(container).Value;

    // If we look at the database, we will see that for this user the discount on the purchase of this film is 5. The movie itself costs 11.
    Assert.AreEqual(6, price, "We expected a different purchase price.");
}

[TestMethod]
[Description("Calculate the cost of a 'My Hero Academia: Heroes Rising' movie for a 'John Cornero' user")]
public void CalculatingCostBuyingMovie_2()
{
    // We have information about the user's mail and the identifier of the film, what he wants to buy.
    string email = "[email protected]";
    int movieId = 1;

    // Let's tell the factory what we know
    var container = new FactContainer
    {
        new UserEmailFact(email),
        new MovieIdFact(movieId),
    };

    // We ask the factory to calculate the cost of buying a movie for our user.
    int price = Factory.DeriveFact<MoviePurchasePriceFact>(container).Value;

    // For this user, the discount for this movie is not configured. Therefore we expect full value.
    Assert.AreEqual(MovieDB.Single(m => m.Id == movieId).Cost, price, "We expected a different purchase price.");
}

How to use a fact factory asynchronously

The fact factory also supports asynchronous operation.

Part 1. Add an asynchronous rule

Let's first create a fact that will be calculated asynchronously (for example, any fact can be calculated this way)

/// <summary>
/// The fact stores information about the cost of buying a movie for the user
/// </summary>
public class MoviePurchasePriceFactAsync : FactBase<int>
{
    public MoviePurchasePriceFactAsync(int value) : base(value)
    {
    }
}

Add a rule for asynchronous computation.

Rules = new FactRuleCollection
{
    //... previous code

    // Let's add a rule to demonstrate asynchronous communication
    async (MovieFact movieFact, MovieDiscountFact movieDiscountFact) =>
    {
        // Simulate work
        await Task.Delay(200);

        return new MoviePurchasePriceFactAsync(movieFact.Value.Cost - movieDiscountFact.Value);
    },
};

Part 2. Extracting the fact

[TestMethod]
[Description("Async calculate the cost of a 'My Hero Academia: Heroes Rising' movie for a 'John Cornero' user.")]
public async Task CalculatingCostBuyingMovie_WithAsyncRule()
{
    // We have information about the user's mail and the identifier of the film, what he wants to buy.
    string email = "[email protected]";
    int movieId = 1;

    // Let's tell the factory what we know
    var container = new FactContainer
    {
        new UserEmailFact(email),
        new MovieIdFact(movieId),
    };

    // We ask the factory to calculate the cost of buying a movie for our user.
    var price = await Factory.DeriveFactAsync<MoviePurchasePriceFactAsync>(container);

    // For this user, the discount for this movie is not configured. Therefore we expect full value.
    Assert.AreEqual(MovieDB.Single(m => m.Id == movieId).Cost, price, "We expected a different purchase price.");
}

That's all. That's the whole algorithm for using.

⚠️ **GitHub.com Fallback** ⚠️