Creating an attached child Task in .NET C#

A child Task, a.k.a a nested Task, is a Task that’s started within the body of another Task. The containing Task is called the parent.

We saw here how to create detached child Tasks. They are not very interesting really. An attached child Task however is one which has a special relationship with the parent:

  • The parent waits for the child to finish before it completes
  • If the child Task throws an exception then the parent will catch it and re-throw it
  • The status of the parent depends on the status of the child

This is how you attach a child task to a parent:

Task parentTask = Task.Factory.StartNew(() =>
{	
        Console.WriteLine("Starting child task...");			
	Task childTask = Task.Factory.StartNew(() =>
	{
		Console.WriteLine("Child running. Going to sleep for a sec.");
		Thread.Sleep(1000);
		Console.WriteLine("Child finished and throws an exception.");
		throw new Exception();
	}, TaskCreationOptions.AttachedToParent);				
});

try
{
	Console.WriteLine("Waiting for parent task");
	parentTask.Wait();
	Console.WriteLine("Parent task finished");
}
catch (AggregateException ex)
{
	Console.WriteLine("Exception: {0}", ex.InnerException.GetType());
}

Note the TaskCreationOptions parameter in the StartNew method: AttachedToParent. If you run this code the you’ll see that the exception thrown by the child task is caught in the try-catch block. The original exception has been packaged within an AggregateException. Also, the Wait() method will wait for the parent task to finish which in turn waits for the child to finish. So Wait() indirectly waits for any attached children the parent Task may have. Note also that as the parent task finishes, its status is updated to ‘WaitingForChildrenToComplete’ which means exactly what it implies.

View the list of posts on the Task Parallel Library here.

Extension to the DDD skeleton project: using an external settings source

Introduction

In this post we saw a way how to introduce caching in the DDD project. We also mentioned that hardcoding the caching duration directly in code may not be desirable for testing and maintenance purposes.

One way to externalise the caching duration is the following setup:

  • We can have a settings file where we set up the caching profiles, such as LongCache = 1 minute, MediumCache = 30sec, ShortCache = 10sec.
  • These profiles can be saved in different places: web.config or app.config, database, customised XML/JSON file, even an external service
  • This is telling us that retrieving the correct setting should be abstracted away

Infrastructure

Open the Infrastructure.Common layer and insert a new folder called Configuration. Insert the following interface:

public interface IConfigurationRepository
{        	
        T GetConfigurationValue<T>(string key);
        string GetConfigurationValue(string key);
        T GetConfigurationValue<T>(string key, T defaultValue);
        string GetConfigurationValue(string key, string defaultValue);
}

We’ll go for the most obvious implementation: the appSettings section in web.config. Insert the following class into the folder:

public class AppSettingsConfigurationRepository : IConfigurationRepository
{        
        public T GetConfigurationValue<T>(string key)
        {
            return GetConfigurationValue(key, default(T), true);
        }

        public string GetConfigurationValue(string key)
        {
            return GetConfigurationValue<string>(key);
        }

        public T GetConfigurationValue<T>(string key, T defaultValue)
        {
            return GetConfigurationValue(key, defaultValue, false);
        }
        
        public string GetConfigurationValue(string key, string defaultValue)
        {
            return GetConfigurationValue<string>(key, defaultValue);
        }

        private T GetConfigurationValue<T>(string key, T defaultValue, bool throwException)
        {
            var value = ConfigurationManager.AppSettings[key];
            if (value == null)
            {
                if(throwException)
                    throw new KeyNotFoundException("Key "+key+ " not found.");
                return defaultValue;
            }
            try
            {
                if (typeof(Enum).IsAssignableFrom(typeof(T)))
                    return (T)Enum.Parse(typeof(T), value);
                return (T)Convert.ChangeType(value, typeof(T));
            }
            catch (Exception ex)
            {
                if (throwException)
                    throw ex;
                return defaultValue;
            }
        }
}

You’ll need to set a reference to the System.Configuration library otherwise ConfigurationManager will not be found.

This is an application of the Adapter pattern which we used in the previous post.

Application service

Locate the EnrichedCustomerService class from the previous post on Service layer caching. Add the following private field:

private readonly IConfigurationRepository _configurationRepository;

Modify the constructor as follows:

public EnrichedCustomerService(ICustomerService innerCustomerService, ICacheStorage cacheStorage, IConfigurationRepository configurationRepository)
{
	if (innerCustomerService == null) throw new ArgumentNullException("CustomerService");
	if (cacheStorage == null) throw new ArgumentNullException("CacheStorage");
	if (configurationRepository == null) throw new ArgumentNullException("ConfigurationRepository");
	_innerCustomerService = innerCustomerService;
	_cacheStorage = cacheStorage;
	_configurationRepository = configurationRepository;
}

We want to read the cache duration setting in GetAllCustomers. Modify the method as follows:

public GetCustomersResponse GetAllCustomers()
{
	string key = "GetAllCustomers";
	GetCustomersResponse response = _cacheStorage.Retrieve<GetCustomersResponse>(key);
	if (response == null)
	{
		int cacheDurationSeconds = _configurationRepository.GetConfigurationValue<int>("ShortCacheDuration");
		response = _innerCustomerService.GetAllCustomers();
		_cacheStorage.Store(key, response, TimeSpan.FromSeconds(cacheDurationSeconds));
	}
	return response;
}

It would be better to have some external container for string values such as ShortCacheDuration, but this will be enough for now.

Web layer

Open the web.config file and enter the following appSetting:

<add key="ShortCacheDuration" value="60"/>

We also need to instruct StructureMap to select AppSettingsConfigurationRepository for any IConfigurationRepository it encounters. Enter the following line into the ObjectFactory.Initialize block of IoC.cs:

x.For<IConfigurationRepository>().Use<AppSettingsConfigurationRepository>();

Set a breakpoint within GetAllCustomers() of EnrichedCustomerService.cs. Set the start page to “customers”: open the Properties of the WebService layer, select Web, Specific Page, “customers”. Start the application.

Code execution will stop at the break point. Step through the code by pressing F11. You’ll see how the settings are extracted from the config file and used with the cache storage mechanism.

Now every time you want to change the cache duration you just update the appropriate settings value. There’s nothing stopping you from inserting different cache duration profiles in your configuration source as mentioned above: ShortCache, LongCache, MediumCache etc. Also, we solved yet another dependency problem in an OOP and SOLID way.

Read the next extension to the DDD project here.

View the list of posts on Architecture and Patterns here.

Creating a detached child Task in .NET C#

A child Task, a.k.a a nested Task, is a Task that’s started within the body of another Task. The containing Task is called the parent.

A detached child Task is one which doesn’t have any relationship with the parent. The detached child Task will be scheduled normally and will have no effect on the parent.

There’s nothing special about creating a detached child Task:

Task parent = Task.Factory.StartNew(() =>
{
	Console.WriteLine("Starting child task...");
	Task childTask = Task.Factory.StartNew(() =>
	{
		Console.WriteLine("Child task running and stopping for a second");
		Thread.Sleep(1000);
		Console.WriteLine("Child task finished");
	});
});

Console.WriteLine("Waiting for parent task");
parent.Wait();
Console.WriteLine("Parent task finished");

We start the parent Task within which we start a child task. You can nest the tasks as you like: the child task can have its own child task(s) which in turn can have child tasks etc.

View the list of posts on the Task Parallel Library here.

Continuation tasks in .NET TPL: exception handling in task chains

We saw in previous posts on TPL how to chain tasks using the ContinueWhenAll and ContinueWhenAny methods. We also discussed strategies of exception handling in tasks. If you don’t know how to use these techniques make sure to read those posts first.

If you have multiple tasks chained together then special care should be taken when handling exceptions. There’s no automatic way to propagate an exception thrown by a task to the continuation task(s). However, the continuation task can always check the status of the antecedent and extract any exceptions. It can then process it in some way.

We start the first task:

Task antecedentTask = Task.Factory.StartNew(() =>
{
	Console.WriteLine("Mother of all tasks.");
});

The first continuation task throws an exception:

Task continuationTaskWithException = antecedentTask.ContinueWith(antecedent =>
{
	Console.WriteLine("Task that throws an exception.");
	throw new Exception();
});

The continuation of the continuation checks the status of the above task and re-throws the inner exception of its aggregate exception:

Task taskToRunAfterExceptionTask = continuationTaskWithException.ContinueWith(antecedent =>
{
	if (antecedent.Status == TaskStatus.Faulted)
	{
		throw antecedent.Exception.InnerException;
	}
	Console.WriteLine("Task that checks if previous task has faulted.");
});

We wait for the second continuation task to finish and handle the aggregate exception in some way:

try
{
	taskToRunAfterExceptionTask.Wait();
}
catch (AggregateException ex)
{
	ex.Handle(innerException =>
	{
		Console.WriteLine("Handled exception is of type: {0}", innerException.GetType());
		return true;
	});
}

If the second continuation task does not check the status of the task before that then the exception thrown by continuationTaskWithException will remain unhandled.

You can handle exceptions in a similar way when doing multitask continuations:

Task<int>[] motherTasks = new Task<int>[10];

Task<int> continuation = Task.Factory.ContinueWhenAll<int>(motherTasks, antecedents =>
{
	foreach (Task<int> task in antecedents)
	{
		if (task.Status == TaskStatus.Faulted)
		{
			//do something with the exception
		}
		else
		{
			//normal operations
		}
	}
	return 1234;
});

Handling exceptions in a ContinueWhenAny scenario is somewhat more complicated. Recall that even if a continuation task will have one antecedent – the one that finishes first – the other tasks in the antecedent task array will continue to run in the background. If one of those background tasks throws an exception then it will remain unhandled. A possible solution is to use ContinueWhenAll only for exception handling purposes:

Task<int>[] motherTasks = new Task<int>[10];

Task continuationTask = Task.Factory.ContinueWhenAny<int>(motherTasks,
	(Task<int> antecedent) =>
	{
		Console.WriteLine("Continuation task.");
	}, TaskContinuationOptions.NotOnFaulted);

Task exceptionHandlingContinuation = Task.Factory.ContinueWhenAll(motherTasks
	, antecedents =>
		{
			foreach (Task task in antecedents)
			{
				if (task.Status == TaskStatus.Faulted)
				{
					//exception handling code here
				}
			}
		});

View the list of posts on the Task Parallel Library here.

Extension to the DDD skeleton project: caching in the service layer

Introduction

This post is a direct continuation of the DDD skeleton project. In this extension we’ll investigate how to incorporate caching into the solution.

Caching can mean several things in an application. It can be introduced at different levels: you can cache database call results, MVC views, service call results, etc. You can save just about any object in one of the cache engines built into .NET.

In this post we’ll concentrate on caching in the Service layer. We’ll try to solve the requirement to cache the result of the following service call of ICustomerService for a period of time:

GetCustomersResponse GetAllCustomers();

A naive but quick solution would be to cache the results directly in the implementing CustomerService class:

public GetCustomersResponse GetAllCustomers()
{
        if (cacheEngine.IncludesObject<GetCustomersResponse>())
        {
              return cacheEngine.RetrieveObject<GetCustomersResponse>();
        }
        else
        {
	     GetCustomersResponse getCustomersResponse = new GetCustomersResponse();
	     IEnumerable<Customer> allCustomers = null;

	     try
	     {
		    allCustomers = _customerRepository.FindAll();
		    getCustomersResponse.Customers = allCustomers.ConvertToViewModels();
                    cacheEngine.SaveObject<GetCustomersResponse>(getCustomersResponse);
	     }
	     catch (Exception ex)
	     {
		    getCustomersResponse.Exception = ex;
	     }
	     return getCustomersResponse;
       }
}

There are a couple of problems with this solution:

  • The method violates the Single Responsibility Principle: it doesn’t only look up all customers but caches them as well
  • The method doesn’t indicate to its consumers what’s going in its method body. The signature states that it will retrieve all customers but there’s nothing about caching. If it returns stale data then the programmer must inspect the method body to find the reason – and will get a surprise: hey, you never told me about caching!
  • It’s difficult to test the service call in isolation: the cache is empty on the first test run but then as the method returns the cached data you cannot test the actual customer retrieval easily. You’d need to clear the cache before every test run. So if the test fails then you won’t know really why it fails: was it because the customer retrieval threw an exception or because the caching engine misbehaved?

Therefore a more thorough solution requires a different way of thinking.

If you are familiar with SOLID and especially the letter ‘D‘ then you’ll know that all this is pointing towards injecting the caching strategy into CustomerService without having to add the caching operations directly within the GetAllCustomers() method body.

There are different ways to solve this problem that we discussed in this post on Interception with their pros and cons:

  • The Decorator pattern
  • AOP
  • Dynamic interception with a DI container

In this post we’ll go for the “purist” Decorator pattern: it is very object oriented and is probably the easiest to follow for a newcomer to this topic. I’m not a fan of AOP – for reasons read the reference above – and dynamic interception is too technical at this level. I don’t want to make a very technical post concentrating on a specific DI container. We’ll learn a lot of new things with the Decorator pattern. In case you’re not at all familiar with the decorator pattern make sure to check out this reference. We’ll also see the Adapter pattern in action to hide the concrete implementation of the caching engine.

The solution we’re going to go through can be applied to a wide range of cross-cutting concerns, such as logging. I’ll present a decorator that adds caching to a service. By the same token you can build a decorator for logging as well. You can then decorate the service class with a logger AND a caching mechanism as well.

Infrastructure

Caching is an example of a cross-cutting concern. Caching can be introduced in many places within a solution and the same caching strategy can be re-used across several applications. This is pointing us towards the Infrastructure layer of the DDD skeleton project.

Also, the technology you choose for caching can vary: use one of the built-in objects in .NET, such as the System.Runtime.Cache, you can use a third part component or write your own custom solution. The point is that the caching engine may change and you’ll want to be able to easily change the concrete implementation. So it sounds like we have a variable dependency that may change over time, and therefore it needs to be hidden behind an abstraction.

Open the Infrastructure.Common layer of the DDD solution and add a new folder called Caching. Add the following interface:

public interface ICacheStorage
{
	void Remove(string key);
	void Store(string key, object data);
        void Store(string key, object data, TimeSpan slidingExpiration);
	void Store(string key, object data, DateTime absoluteExpiration, TimeSpan slidingExpiration);
	T Retrieve<T>(string key);
}

All cache storage mechanisms will need to implement this interface. We’ll go for the highly efficient, general-purpose caching engine of System.Runtime.Caching in the first implementation. Add the following class to the Caching folder:

public class SystemRuntimeCacheStorage : ICacheStorage
{
	public void Remove(string key)
	{
		ObjectCache cache = MemoryCache.Default;
		cache.Remove(key);
	}

	public void Store(string key, object data)
	{
		ObjectCache cache = MemoryCache.Default;
		cache.Add(key, data, null);
	}

	public void Store(string key, object data, DateTime absoluteExpiration, TimeSpan slidingExpiration)
	{
		ObjectCache cache = MemoryCache.Default;
		var policy = new CacheItemPolicy
		{
			AbsoluteExpiration = absoluteExpiration,
			SlidingExpiration = slidingExpiration
		};

		if (cache.Contains(key))
		{
			cache.Remove(key);
		}

		cache.Add(key, data, policy);
	}

	public T Retrieve<T>(string key)
	{
		ObjectCache cache = MemoryCache.Default;
		return cache.Contains(key) ? (T)cache[key] : default(T);
	}

        public void Store(string key, object data, TimeSpan slidingExpiration)
	{
		ObjectCache cache = MemoryCache.Default;
		var policy = new CacheItemPolicy
		{
			SlidingExpiration = slidingExpiration
		};

		if (cache.Contains(key))
		{
			cache.Remove(key);
		}

		cache.Add(key, data, policy);
	}
}

The code may not compile at first as ObjectCache is located in an unreferenced library. Add a reference to the System.Runtime.Caching dll in the Infrastructure project.

Decorator pattern in the ApplicationServices layer

OK, now we have the caching elements in place using the Adapter pattern. Next we want to enrich the existing CustomerService implementation of the ICustomerService interface to include caching. In the following stub we’ll delegate most of the methods to the inner customer service, which will be our “normal” CustomerService class – we’ll look at the GetAllCustomers() method in a sec. Insert the following class into the Implementations folder:

public class EnrichedCustomerService : ICustomerService
{
	private readonly ICustomerService _innerCustomerService;
	private readonly ICacheStorage _cacheStorage;

	public EnrichedCustomerService(ICustomerService innerCustomerService, ICacheStorage cacheStorage)		
	{
		if (innerCustomerService == null) throw new ArgumentNullException("CustomerService");
		if (cacheStorage == null) throw new ArgumentNullException("CacheStorage");
		_innerCustomerService = innerCustomerService;
		_cacheStorage = cacheStorage;
	}

	public GetCustomerResponse GetCustomer(GetCustomerRequest getCustomerRequest)
	{
		return _innerCustomerService.GetCustomer(getCustomerRequest);
	}

	public GetCustomersResponse GetAllCustomers()
	{
		throw new NotImplementedException();
	}

	public InsertCustomerResponse InsertCustomer(InsertCustomerRequest insertCustomerRequest)
	{
		return _innerCustomerService.InsertCustomer(insertCustomerRequest);
	}

	public UpdateCustomerResponse UpdateCustomer(UpdateCustomerRequest updateCustomerRequest)
	{
		return _innerCustomerService.UpdateCustomer(updateCustomerRequest);
	}

	public DeleteCustomerResponse DeleteCustomer(DeleteCustomerRequest deleteCustomerRequest)
	{
		return _innerCustomerService.DeleteCustomer(deleteCustomerRequest);
	}
}

Again, if you don’t understand the structure of this class check out the post on the Decorator pattern. Here’s the implementation of the GetAllCustomers() method:

public GetCustomersResponse GetAllCustomers()
{
	string key = "GetAllCustomers";
	GetCustomersResponse response = _cacheStorage.Retrieve<GetCustomersResponse>(key);
	if (response == null)
	{
		response = _innerCustomerService.GetAllCustomers();
		_cacheStorage.Store(key, response, TimeSpan.FromMinutes(1));
	}
	return response;
}

We check in the cache if a GetCustomersResponse object is present and return it. Otherwise we ask the inner customer service to fetch all customers and put the result into the cache for 1 minute – we’ll come back to this point at the end of the post.

We are done with the decorated CustomerService class. Now we’d like to make sure that the CustomerController receives the EnrichedCustomerService instead of CustomerService.

StructureMap

Recall that we’re using StructureMap as our DI container. At present it follows the convention that whenever it sees an interface dependency whose name starts with “I” it will look for a concrete implementation with the same name without the “I”: IProductService == ProcuctService, ICustomerRepository == CustomerRepository. We also saw examples of specifying a concrete class that StructureMap will insert:

x.For<IUnitOfWork>().Use<InMemoryUnitOfWork>();
x.For<IObjectContextFactory>().Use<LazySingletonObjectContextFactory>();

So we should be able to add the following code, right?

x.For<ICustomerService>().Use<EnhancedCustomerService>();

Keep in mind that EnhancedCustomerService also has a dependency on ICustomerService so this code would inject another EnhancedCustomerService into EnhancedCustomerService which is not what we want. The goal is the following:

  • The CustomerController class should get EnhancedCustomerService from StructureMap
  • EnhancedCustomerService should get CustomerService from StructureMap

In fact this is such a common problem that it has been solved multiple times in StructureMap. All decent DI containers provide a solution for the Decorator pattern. It can be solved in at least 3 ways in StructureMap:

  • Decorating with instance references
  • Decorating with named instances
  • Decorating with delegates

This is not a technical post about StructureMap so I won’t go into any detail. We’ll go for the first option. Locate the IoC.cs class in the WebService layer and add the the following piece of code the the ObjectFactory.Initialize block:

x.For<ICacheStorage>().Use<SystemRuntimeCacheStorage>();
var customerService = x.For<ICustomerService>().Use<CustomerService>();
x.For<ICustomerService>().Use<EnrichedCustomerService>().Ctor<ICustomerService>().Is(customerService);

The first row is quite clear: we want to use the SystemRuntimeCacheStorage implementation of the ICacheStorage interface. The second row indicates that we want to use CustomerService where ICustomerService is used and retain a reference to the object returned by the Use method: a SmartInstance of T which is a StructureMap object. In the third row we modify this statement by saying that for ICustomerService we want an EnrichedCustomerService object and inject the retained customerService in its constructor. The other elements in the EnrichedCustomerService constructor will be resolved automatically by StructureMap. This looks a bit cryptic at first but that’s how it’s done, period.

Open the Properties window of the WebService layer, select the Web tag, click “Specific Page” and enter “customers” as the start-up page. Set a couple of breakpoints:

  • One withing the Get() method of CustomersController
  • Another one within GetAllCustomers() of EnrichedCustomerService

Start the web service and the start up URL in the browser should be something like http://localhost:9985/customers. The port number will probably be different in your case. Code execution should stop at the first breakpoint in the Get() method. Inspect the type of the injected ICustomerService in CustomersController.cs, it should be of type EnrichedCustomerService. Let the code continue and execution will stop again at GetAllCustomers in EnrichedCustomerService. Inspect the type of the inner customer service in that class: it should be of CustomerService. From here on I encourage you to step through the code with F11 to see how caching is used. Then refresh the page in the browser and you’ll see that the GetCustomersResponse is fetched from the cache instead of asking the CustomerService instance.

That’s about it. We have solved the caching problem in an object oriented, testable and SOLID way.

Further enhancement

There’s still room for improvement in our cache solution. Right now the cache duration is hard coded here:

_cacheStorage.Store(key, response, TimeSpan.FromMinutes(1));

It would be better with a more configurable solution where you can change the duration without coming back to this class every time. Imagine that you have caching in hundreds of classes – changing the duration in every single one will not make a happy programmer.

There are several ways to solve this problem. The next post will present a possible solution.

View the list of posts on Architecture and Patterns here.

Continuation tasks in .NET TPL: cancelling continuation tasks

Tasks in .NET TPL make it easy to assign tasks that should run upon the completion of a certain task.

We saw in previous posts on TPL how to define continuation tasks. You’ll also find examples showing how to cancel tasks in TPL. Continuation tasks can be cancelled using the same techniques as with “normal” tasks. If you don’t know the basics, then make sure to check out the related posts on this page.

We declare the cancellation token source:

CancellationTokenSource cancellationTokenSource	= new CancellationTokenSource();

We then continue with the antecedent task and provide the token source in the constructor:

Task motherTask = Task.Factory.StartNew(() =>
{
	Console.WriteLine("Mother task running.");
	cancellationTokenSource.Token.WaitHandle.WaitOne();
	cancellationTokenSource.Token.ThrowIfCancellationRequested();
}, cancellationTokenSource.Token);

We wait indefinitely for the token source to be cancelled with the WaitOne() method.

The following continuation task is created with the same cancellation token. This has the effect that if the token source is cancelled then both the antecedent and the continuation tasks will be cancelled:

Task taskSharingCancellationToken = motherTask.ContinueWith(antecedent =>
{
	Console.WriteLine("This continuation task will never run as it shares the cancellation token with the antecedent.");
}, cancellationTokenSource.Token);

If you remove the token from the constructor then this continuation task will run after the antecedent has completed with an exception.

The following continuation task may seem correct at first as it should only run if the antecedent has been cancelled:

Task incorrectContinuation = motherTask.ContinueWith(antecedent =>
{
       Console.WriteLine("This task will never be scheduled");
}, cancellationTokenSource.Token, TaskContinuationOptions.OnlyOnCanceled, TaskScheduler.Current);

However, it also shares the same cancellation token as the antecedent, so it will never run. In other words if you want a continuation task to run even if the antecedent has been cancelled then do not share the same cancellation token across tasks.

The below task will run as it doesn’t share the cancellation token:

Task correctContinuation = motherTask.ContinueWith(antecedent =>
{
	Console.WriteLine("Continuation running as there's no cancellation token sharing.");
}, TaskContinuationOptions.OnlyOnCanceled);

We cancel the token and wait for the correct continuation task to finish:

cancellationTokenSource.Cancel();
correctContinuation.Wait();

View the list of posts on the Task Parallel Library here.

Extension to the DDD skeleton project: domain specific rules Part 2

Introduction

In the first part of this DDD skeleton project extension we completed the domain and repository layers. The repository implementation was only a stub as we want to concentrate on validating domains with differing regional rules.

In this post we’ll finish off the communication chain in the skeleton web service.

Let’s get started! Open the DDD project and we’ll continue with the application service layer.

The Application services layer

We’ll concentrate on inserting new CountrySpecificCustomer objects in the data store. We won’t support the entire GET/POST/PUT/DELETE spectrum. You can do all that based on how we did it for the simple Customer object. Locate the Messaging folder and insert a new sub-folder called EnhancedCustomers. Insert the following messaging objects in this new folder:

public class CountrySpecificCustomerViewModel
{
	public string FirstName { get; set; }
	public int Age { get; set; }
	public string NickName { get; set; }
	public string Email { get; set; }
	public string CountryCode { get; set; }
}

public class InsertCountrySpecificCustomerRequest : ServiceRequestBase
{
	public CountrySpecificCustomerViewModel CountrySpecificCustomer { get; set; }
}

public class InsertCountrySpecificCustomerResponse : ServiceResponseBase
{}

Nothing fancy here I hope. These objects follow the same structure as their original Customer counterparts.

Add the following interface in the Interfaces folder:

public interface ICountrySpecificCustomerService
{
	InsertCountrySpecificCustomerResponse InsertCountrySpecificCustomer(InsertCountrySpecificCustomerRequest insertCustomerRequest);
}

Recall that the constructor of the CountrySpecificCustomer object is expecting an instance of Country. We already have a factory in the Domain layer that returns the correct country based on the country code. So we *could* use that factory here as well since the CountrySpecificCustomerViewModel allows for the specification of a country code. However, it would be unfortunate to use that concrete factory in the Services layer as it would only increase the degree of coupling between the Services and the Domain layer. So we’ll hide it behind an abstraction.

Locate the folder where you saved the CountryFactory object in the Domain layer. Insert the following interface:

public interface ICountryFactory
{
	Country CreateCountry(string countryCode);
}

The existing CountryFactory can implement the interface as follows:

public class CountryFactory : ICountryFactory
{
	private static IEnumerable<Country> AllCountries()
	{
		return new List<Country>() { new Hungary(), new Germany(), new Sweden() };
	}

	public static Country Create(string countryCode)
	{
		return (from c in AllCountries() where c.CountryCode.ToLower() == countryCode.ToLower() select c).FirstOrDefault();
	}

	public Country CreateCountry(string countryCode)
	{
		return Create(countryCode);
	}
}

Back in the services layer we can implement the ICountrySpecificCustomerService interface. Just to refresh our minds open the CustomerService class in the Implementations folder. You’ll recall that its constructor requires an IUnitOfWork object. The implementation of the ICountrySpecificCustomerService will also need one. This calls for a base class which both implementations can derive from. Insert the following abstract class in the Implementations folder:

public abstract class ApplicationServiceBase
{
	private readonly IUnitOfWork _unitOfWork;

	public ApplicationServiceBase(IUnitOfWork unitOfWork)
	{
		if (unitOfWork == null) throw new ArgumentNullException("UnitOfWork");
		_unitOfWork = unitOfWork;
	}

	public IUnitOfWork UnitOfWork
	{
		get
		{
			return _unitOfWork;
		}
	}
}

Next modify the declaration and constructor of the CustomerService object as follows:

public class CustomerService : ApplicationServiceBase, ICustomerService
{
	private readonly ICustomerRepository _customerRepository;

	public CustomerService(ICustomerRepository customerRepository, IUnitOfWork unitOfWork)
		: base(unitOfWork)
	{
		if (customerRepository == null) throw new ArgumentNullException("Customer repo");
		_customerRepository = customerRepository;
	}
.//implementations omitted
.
.
}

After this change you’ll get some errors saying that the _unitOfWork variable is not accessible, e.g. here:

_unitOfWork.Commit();

Replace those calls by referring to the public accessor of the base class:

UnitOfWork.Commit();

Add a new class to the Implementations folder called CountrySpecificCustomerService. Insert the following implementation stub:

public class CountrySpecificCustomerService : ApplicationServiceBase, ICountrySpecificCustomerService
{
	private readonly ICountrySpecificCustomerRepository _repository;
	private readonly ICountryFactory _countryFactory;

	public CountrySpecificCustomerService(IUnitOfWork unitOfWork, ICountrySpecificCustomerRepository repository
		, ICountryFactory countryFactory) : base(unitOfWork)
	{
		if (repository == null) throw new ArgumentNullException("CountrySpecificCustomerRepository");
		if (countryFactory == null) throw new ArgumentNullException("ICountryFactory");
		_repository = repository;
		_countryFactory = countryFactory;
	}

	public InsertCountrySpecificCustomerResponse InsertCountrySpecificCustomer(InsertCountrySpecificCustomerRequest insertCustomerRequest)
	{
		throw new NotImplementedException();
	}
}

In the InsertCountrySpecificCustomer implementation we’ll follow the same structure as in CustomerService.InsertCustomer: construct the domain object, validate it and then insert it. Insert the following implementation:

public InsertCountrySpecificCustomerResponse InsertCountrySpecificCustomer(InsertCountrySpecificCustomerRequest insertCustomerRequest)
{
	CountrySpecificCustomer newCustomer = BuildCountrySpecificCustomer(insertCustomerRequest.CountrySpecificCustomer);
	ThrowExceptionIfCustomerIsInvalid(newCustomer);
	try
	{
		_repository.Insert(newCustomer);
		UnitOfWork.Commit();
		return new InsertCountrySpecificCustomerResponse();
	}
	catch (Exception ex)
	{
		return new InsertCountrySpecificCustomerResponse() { Exception = ex };
	}
}

BuildCountrySpecificCustomer and ThrowExceptionIfCustomerIsInvalid look as follows:

private CountrySpecificCustomer BuildCountrySpecificCustomer(CountrySpecificCustomerViewModel viewModel)
{
	return new CountrySpecificCustomer(_countryFactory.CreateCountry(viewModel.CountryCode))
	{
		Age = viewModel.Age
		, Email = viewModel.Email
		, FirstName = viewModel.FirstName
		, NickName = viewModel.NickName
	};
}

private void ThrowExceptionIfCustomerIsInvalid(CountrySpecificCustomer newCustomer)
{
	IEnumerable<BusinessRule> brokenRules = newCustomer.GetBrokenRules();
	if (brokenRules.Count() > 0)
	{
		StringBuilder brokenRulesBuilder = new StringBuilder();
		brokenRulesBuilder.AppendLine("There were problems saving the LoadtestPortalCustomer object:");
		foreach (BusinessRule businessRule in brokenRules)
		{
			brokenRulesBuilder.AppendLine(businessRule.RuleDescription);
		}

		throw new Exception(brokenRulesBuilder.ToString());
	}
}

New elements in the Web layer

We’re now ready to construct our new Controller. Locate the Controllers folder in the WebService project of the solution. Add a new empty ApiController called CountrySpecificCustomersController. Add the following private field and constructor:

private readonly ICountrySpecificCustomerService _countrySpecificCustomerService;

public CountrySpecificCustomersController(ICountrySpecificCustomerService countrySpecificCustomerService)
{
	if (countrySpecificCustomerService == null) throw new ArgumentNullException("CountrySpecificCustomerService");
	_countrySpecificCustomerService = countrySpecificCustomerService;
}

We’ll only support the POST operation so that we can concentrate on validation. Add the following Post method to the controller:

public HttpResponseMessage Post(CountrySpecificCustomerViewModel viewModel)
{
	ServiceResponseBase response = _countrySpecificCustomerService.InsertCountrySpecificCustomer(new InsertCountrySpecificCustomerRequest() { CountrySpecificCustomer = viewModel });
	return Request.BuildResponse(response);
}

That’s it actually, we can now test the new parts using the simple Console tool we built in the last post of the DDD series. Start the DDD web service and take note of the localhost URL in the web browser. Insert a breakpoint within the Post method of the CountrySpecificCustomersController class so that you can go through the code step by step.

Open that little test application and add the following class to the main project:

public class CountrySpecificCustomerViewModel
{
	public string FirstName { get; set; }
	public int Age { get; set; }
	public string NickName { get; set; }
	public string Email { get; set; }
	public string CountryCode { get; set; }
}

Insert two methods that will build a valid and an invalid country specific customer:

private static CountrySpecificCustomerViewModel BuildFailingCustomer()
{
	return new CountrySpecificCustomerViewModel()
	{
		FirstName = "Elvis"
		, Age = 17
		, CountryCode = "SWE"
	};
}

private static CountrySpecificCustomerViewModel BuildPassingCustomer()
{
	return new CountrySpecificCustomerViewModel()
	{
		FirstName = "John"
		, Age = 19
		, CountryCode = "SWE"
	};
}

Recall that Swedish customers must be over 18, so the insertion of the first object should fail. Insert the following test method to Program.cs:

private static void TestCountrySpecificCustomerValidation()
{
	HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, _serviceUriTwo);
	requestMessage.Headers.ExpectContinue = false;
	CountrySpecificCustomerViewModel vm = BuildFailingCustomer();
	string jsonInputs = JsonConvert.SerializeObject(vm);
	requestMessage.Content = new StringContent(jsonInputs, Encoding.UTF8, "application/json");
	HttpClient httpClient = new HttpClient();
	httpClient.Timeout = new TimeSpan(0, 10, 0);
	Task<HttpResponseMessage> httpRequest = httpClient.SendAsync(requestMessage,
			HttpCompletionOption.ResponseContentRead, CancellationToken.None);
	HttpResponseMessage httpResponse = httpRequest.Result;
	HttpStatusCode statusCode = httpResponse.StatusCode;
	HttpContent responseContent = httpResponse.Content;
	if (responseContent != null)
	{
		Task<String> stringContentsTask = responseContent.ReadAsStringAsync();
		String stringContents = stringContentsTask.Result;
		Console.WriteLine("Response from service: " + stringContents);
	}
	Console.ReadKey();
}

…where _serviceUriTwo is the URL of the controller. It should be something like this:

private static Uri _serviceUriTwo = new Uri("http://localhost:9985/countryspecificcustomers");

So we’ll be testing the failing customer first. Insert a call to this test method from Main and press F5. If you entered the breakpoint mentioned above then the code execution should stop there. Step through the code and when it hits the validation part in the application service layer – the ThrowExceptionIfCustomerIsInvalid method – you should see a validation error: There were problems saving the LoadtestPortalCustomer object: Swedish customers must be at least 18.

Next replace the following…

CountrySpecificCustomerViewModel vm = BuildFailingCustomer();

…with…

CountrySpecificCustomerViewModel vm = BuildPassingCustomer();

…within the TestCountrySpecificCustomerValidation() method and run the test again. Validation should succeed without any problems.

Feel free to test the code with the other cases we set up in the original specifications by playing with the property values of the CountrySpecificCustomerViewModel object we send to the service.

That’s all folks. We’ll look at caching in the next extension of the DDD project.

View the list of posts on Architecture and Patterns here.

Continuation tasks in .NET TPL: any task in an array continued by a single task

Tasks in .NET TPL make it easy to assign tasks that should run upon the completion of a certain task.

In this post we saw how to create many tasks followed by a single continuation task that runs after all the antecedents have completed. We can also have the following scenario: a group of antecedent tasks are started and a single continuation task will be scheduled to run when the first task in the array of antecedent tasks has completed. You use the ContinueWhenAny() method to achieve this.

Make sure to read the post referred to above as ContinueWhenAll() and ContinueWhenAny() are very similar. Read especially the section on return values as it also applies to this post: just replace “ContinueWhenAll” with “ContinueWhenAny” in the examples. Also, read this post to apply a condition when the continuation task should be scheduled.

In the below example we’ll create a range of tasks that all return an integer: the number of milliseconds they were put to sleep. A CancellationToken object is used to introduce a random pause whose length is randomised between 100 and 1000 milliseconds. The access to the Random object is synchronised by a synchronisation primitive.

We declare a couple of elements first:

Task<int>[] motherTasks = new Task<int>[10];
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

Random random = new Random();
Object syncPrimitive = new object();

The task array is filled and each task is started and returns an integer:

for (int i = 0; i < 10; i++)
{
	motherTasks[i] = Task.Factory.StartNew<int>(() =>
	{
		int taskSleepInterval;
		lock (syncPrimitive)
		{
			taskSleepInterval = random.Next(100, 1000);
		}
		cancellationTokenSource.Token.WaitHandle.WaitOne(taskSleepInterval);
		cancellationTokenSource.Token.ThrowIfCancellationRequested();
		return taskSleepInterval;
	}, cancellationTokenSource.Token);
}

Next we declare the continuation task:

Task continuationTask = Task.Factory.ContinueWhenAny<int>(motherTasks,
	(Task<int> antecedent) =>
	{
		Console.WriteLine("The first task slept for {0} milliseconds",
			antecedent.Result);
	});

The continuation task will be scheduled to run as soon as the first task in the antecedent task array has completed.

We wait for the continuation task to finish and then cancel the token source:

continuationTask.Wait();
cancellationTokenSource.Cancel();

Run the code and you’ll see that the continuation task reports the result from the first task.

View the list of posts on the Task Parallel Library here.

Continuation tasks in .NET TPL: many tasks continued by a single task

Tasks in .NET TPL make it easy to assign tasks that should run upon the completion of a certain task.

We’ll need a basic object with a single property:

public class Series
{
	public int CurrentValue
	{
		get;
		set;
	}
}

In previous installments of the posts on TPL we saw how to create tasks that are scheduled to run after a single task has completed: check here and here. It is also possible to create many tasks and a single continuation task that runs after all the antecedents have completed. The ContinueWhenAll() method takes an array or Task objects, all of which have to complete before the continuation task can proceed.

We create 10 tasks each of which increases the its local series value by 1000 in total in a loop:

Series series = new Series();

Task<int>[] motherTasks = new Task<int>[10];

for (int i = 0; i < 10; i++)
{
	motherTasks[i] = Task.Factory.StartNew<int>((stateObject) =>
	{
		int seriesValue = (int)stateObject;
		for (int j = 0; j < 1000; j++)
		{
			seriesValue++;
		}
		return seriesValue;
	}, series.CurrentValue);
}

All 10 tasks are antecedent tasks in the chain. Let’s declare the continuation task:

Task continuation = Task.Factory.ContinueWhenAll<int>(motherTasks, antecedents =>
{
	foreach (Task<int> task in antecedents)
	{
		series.CurrentValue += task.Result;
	}
});

continuation.Wait();
Console.WriteLine(series.CurrentValue);

We extract the series value from each antecedent task and increase the overall series value by the individual task results, i.e. a 1000. The lambda expression looks awkward a bit: the array of antecedent tasks appears twice. Once as the first argument to the ContinueWhenAll method and then as an input to the lambda expression.

There’s one more thing to note about the syntax: ContinueWhenAll of T denotes the return type of the antecedent tasks. You can also specify the return type of the continuation. Here’s a list of possibilities:

The continuation and the antecedent tasks both return an int:

Task<int> continuation = Task.Factory.ContinueWhenAll<int>(motherTasks, antecedents =>
{
	foreach (Task<int> task in antecedents)
	{
		series.CurrentValue += task.Result;
	}
	return 1234;
});

Continuation is void and antecedent tasks return an int:

Task continuation = Task.Factory.ContinueWhenAll<int>(motherTasks, antecedents =>
{
	foreach (Task<int> task in antecedents)
	{
		series.CurrentValue += task.Result;
	}
});

All of them are void:

Task continuation = Task.Factory.ContinueWhenAll(motherTasks, antecedents =>
{
	foreach (Task<int> task in antecedents)
	{
		//do something
	}
});

Continuation returns an int and antecedent tasks are void:

Task<int> continuation = Task<int>.Factory.ContinueWhenAll(motherTasks, antecedents =>
{
	foreach (Task<int> task in antecedents)
	{
		//do something
	}
	return 1234;
});

Note that it’s enough to start the antecedent tasks, which we did with Task.Factory.StartNew. The continuation task will be scheduled automatically.

View the list of posts on the Task Parallel Library here.

Extension to the DDD skeleton project: domain specific rules Part 1

Introduction

This post is a direct continuation of the series of posts on Domain Driven Design. Johannes asked in this post how to include rules in the domain that may vary from instance to instance. The idea is that the Validate() method may need to check different types of rules for a domain. I provided a simplified answer with code examples in my answer but I’d like to formalise the solution within the DDD skeleton project.

The scenario is the following:

  • We have an international e-commerce site where customers can order goods
  • The data required from a customer on the order form can vary depending on the country they are ordering from
  • We need to make sure that the country specific rules are enforced when a customer is created

There are potentially many countries the customers can order from. Let’s see some possible solutions that can come to one’s mind:

  • Extend the abstract Validate method in the EntityBase class so that it accepts some context parameter where we can send in some country identifier. The implemented method can check the context parameter and validate the Customer object in a series of if-else statements: if (country == “GER”) then. etc. That would result in a potentially very long if-else statement if we have a large number of countries on our commercial map. That is clearly not maintainable and does not properly reflect the importance of the rule. We would end up with a bloated and hard-to-test Customer class. Also, all other domain objects would have to implement the overloaded Validate method and send in some parameter that may not even be used. Therefore we can quickly rule out this option.
  • Make Customer abstract and create country specific concrete classes, such as GermanCustomer, FrenchCustomer etc. That would result in a very messy class structure and the importance of the country rule would still be hidden.
  • Continue with the current Customer class and let some external service do an extra validation on its behalf. One of the lessons we’ve learned from the DDD project is that each domain object should contain its own logic and should be able to validate itself. Domain logic should not be spread out in the solution, especially not in assemblies outside the domain

After dismissing the above proposals we come to the conclusion that we need a more object oriented solution and we need to elevate the country specific rule to an “objectified” form.

New components in the Domain layer

We’ll introduce a new domain: Country. This object won’t need an ID: we don’t need to differentiate between two Country objects with the same country code and country name. Therefore it will be a value object. Add the following class to the ValueObjects folder of the Domain layer:

public abstract class Country : ValueObjectBase
{
	public abstract string CountryCode { get; }
	public abstract string CountryName { get; }

	protected override void Validate()
	{
		if (string.IsNullOrEmpty(CountryCode)) AddBrokenRule(ValueObjectBusinessRule.CountryCodeRequired);
		if (string.IsNullOrEmpty(CountryName)) AddBrokenRule(ValueObjectBusinessRule.CountryNameRequired);
	}
}

The 2 new business rules in the ValueObjectBusinessRule container are as follows:

public static readonly BusinessRule CountryCodeRequired = new BusinessRule("Country must have a country code");
public static readonly BusinessRule CountryNameRequired = new BusinessRule("Country must have a name");

We won’t be adding new countries through some service call so the Validate method implementation is not strictly required. However it’s good to have the Validate method ready for the future.

Create 3 specific countries:

public class Germany : Country
{
	public override string CountryCode
	{
		get { return CountryCodes.Germany; }
	}

	public override string CountryName
	{
		get { return "Germany"; }
	}
}

public class Hungary : Country
{
	public override string CountryCode
	{
		get { return CountryCodes.Hungary; }
	}

	public override string CountryName
	{
		get { return "Hungary"; }
	}
}

public class Sweden : Country
{
	public override string CountryCode
	{
		get { return CountryCodes.Sweden; }
	}

        public override string CountryName
	{
		get { return "Sweden"; }
	}
}

The country codes are maintained in a separate container class:

public class CountryCodes
{
	public readonly static string Germany = "GER";
	public readonly static string Hungary = "HUN";
	public readonly static string Sweden = "SWE";
}

In reality the codes will probably be maintained and retrieved from a data store, but this quick solution will do for the time being.

We’ll let a factory return the specific country implementations:

public class CountryFactory
{
	private static IEnumerable<Country> AllCountries()
	{
		return new List<Country>() { new Hungary(), new Germany(), new Sweden() };
	}

	public static Country Create(string countryCode)
	{
		return (from c in AllCountries() where c.CountryCode.ToLower() == countryCode.ToLower() select c).FirstOrDefault();
	}
}

Let’s say we have the following country-related Customer rules:

  • All customers must have a first name
  • Swedish customers must be over 18
  • Hungarian customers must have a nickname
  • German customers must have an email address

The country specific rules will derive from the following base class:

public abstract class CountrySpecificCustomerRule
{
	public abstract Country Country { get; }
	public abstract List<BusinessRule> GetBrokenRules(CountrySpecificCustomer customer);
}

I’ve decided not to touch the Customer object that we created earlier in this series. We can have it for reference. Instead we have a new domain object: CountrySpecificCustomer. We’ll add that new class in a second. Before that let’s create the country specific implementations of the abstract rule:

public class GermanCustomerRule : CountrySpecificCustomerRule
{
	public override Country Country
	{
		get { return CountryFactory.Create(CountryCodes.Germany); }
	}

	public override List<BusinessRule> GetBrokenRules(CountrySpecificCustomer customer)
	{
		List<BusinessRule> brokenRules = new List<BusinessRule>();
		if (string.IsNullOrEmpty(customer.Email))
		{
			brokenRules.Add(new BusinessRule("German customers must have an email"));
		}
		return brokenRules;
	}
}

public class HungarianCustomerRule : CountrySpecificCustomerRule
{
	public override Country Country
	{
		get { return CountryFactory.Create(CountryCodes.Hungary); }
	}

	public override List<BusinessRule> GetBrokenRules(CountrySpecificCustomer customer)
	{
		List<BusinessRule> brokenRules = new List<BusinessRule>();
		if (string.IsNullOrEmpty(customer.NickName))
		{
			brokenRules.Add(new BusinessRule("Hungarian customers must have a nickname"));
		}
		return brokenRules;
	}
}

public class SwedishCustomerRule : CountrySpecificCustomerRule
{
	public override Country Country
	{
		get { return CountryFactory.Create(CountryCodes.Sweden); }
	}

	public override List<BusinessRule> GetBrokenRules(CountrySpecificCustomer customer)
	{
		List<BusinessRule> brokenRules = new List<BusinessRule>();
		if (customer.Age < 18)
		{
			brokenRules.Add(new BusinessRule("Swedish customers must be at least 18."));
		}
		return brokenRules;
	}
}

You’ll see that the implemented GetBrokenRules() methods reflect the country-specific requirements listed above.

The creation of the correct rule implementation will come from another factory:

public class CountrySpecificCustomerRuleFactory
{
	private static IEnumerable<CountrySpecificCustomerRule> GetAllCountryRules()
	{
		List<CountrySpecificCustomerRule> implementingRules = new List<CountrySpecificCustomerRule>()
		{
			new HungarianCustomerRule()
			, new SwedishCustomerRule()
			, new GermanCustomerRule()
		};
		return implementingRules;
	}

	public static CountrySpecificCustomerRule Create(Country country)
	{
		return (from c in GetAllCountryRules() where c.Country.CountryCode == country.CountryCode select c).FirstOrDefault();
	}
}

We’re now ready to insert the new domain:

public class CountrySpecificCustomer : EntityBase<int>, IAggregateRoot
{
	private Country _country;

	public CountrySpecificCustomer(Country country)
	{
		_country = country;
	}

	public string FirstName { get; set; }
	public int Age { get; set; }
	public string NickName { get; set; }
	public string Email { get; set; }

	protected override void Validate()
	{
		//overall rule
		if (string.IsNullOrEmpty(FirstName))
		{
			AddBrokenRule(new BusinessRule("All customers must have a first name"));
		}
		List<BusinessRule> brokenRules = new List<BusinessRule>();
		brokenRules.AddRange(CountrySpecificCustomerRuleFactory.Create(_country).GetBrokenRules(this));
		foreach (BusinessRule brokenRule in brokenRules)
		{
			AddBrokenRule(brokenRule);
		}
	}
}

We’ll also need an abstract repository:

public interface ICountrySpecificCustomerRepository : IRepository<CountrySpecificCustomer, int>
{
}

This completes the extension to the Domain layer.

New elements in the Repository.Memory layer

We’ll add a couple of stub implementations and objects related to the ICountrySpecificCustomerRepository interface. The goal is to demonstrate how the specific rules can be enforced across different CountrySpecificCustomer instances. We’ll not waste time around building a dummy repository around CountrySpecificCustomer like we did with the Customer object in the series. The implementations would be very similar anyway.

Enter the following empty database representation of the CountrySpecificCustomer object in the Database folder of the Repository layer:

public class DatabaseCountrySpecificCustomer
{
	//leave it empty
}

Enter the following stub implementation of the ICountrySpecificCustomerRepository interface:

public class CountrySpecificCustomerRepository : Repository<CountrySpecificCustomer, int, DatabaseCountrySpecificCustomer>
	, ICountrySpecificCustomerRepository
{
	public CountrySpecificCustomerRepository(IUnitOfWork unitOfWork, IObjectContextFactory objectContextFactory)
		: base(unitOfWork, objectContextFactory)
	{}

	public override CountrySpecificCustomer FindBy(int id)
	{
		return null;
	}

	public override DatabaseCountrySpecificCustomer ConvertToDatabaseType(CountrySpecificCustomer domainType)
	{
		return null;
	}

	public IEnumerable<CountrySpecificCustomer> FindAll()
	{
		return null;
	}
}

Again, we don’t care about the implementation of the Find methods. You can check the CustomerRepository for reference if you’d like to practice.

We’ll continue with the Service and Web layers and the tests in the next post.

View the list of posts on Architecture and Patterns here.

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

ARCHIVED: Bite-size insight on Cyber Security for the not too technical.