A model SOA application in .NET Part 2: the domain

Introduction

The SOA model application will simulate a Product purchase system. The Product must be reserved first and the purchase must be then confirmed. So this is a scenario familiar from e-commerce sites, although in reality the flow may be more complicated. The point here is that the purchase is not a one-step process and the web service must be able to track the registration somehow. So the Product can be anything you can think of: tickets, books, whatever, that’s a detail we don’t care about.

We’ll continue from the previous post which set the theoretical basis for the model we’re about to build.

If you have followed along the series on DDD then you’ll notice that the Domain structure I’m going to present is a lot simpler: there’s no aggregate root and entity base etc., and that’s for a good reason. Those belong to the DDD-specific discussion whereas here we want to concentrate on SOA. Therefore I decided to remove those elements that might distract us from the main path and keep the Domain layer simple. Otherwise if you are not familiar with DDD but still want to learn about SOA you may find the code hard to follow.

Demo

Open Visual Studio and insert a new blank solution called SoaIntroNet. Add a C# class library called SoaIntroNet.Domain to it and remove Class1.cs. Add a folder called ProductDomain and a class called Product in it. Here’s the Product domain code with some explanation to follow. The code will not compile at first as we need to implement some other objects as well in a bit.

public class Product
{
	public Product()
	{
		ReservedProducts = new List<ProductReservation>();
		PurchasedProducts = new List<ProductPurchase>();
	}

	public Guid ID { get; set; }
	public string Name { get; set; }
	public string Description { get; set; }
	public int Allocation { get; set; }
	public List<ProductReservation> ReservedProducts { get; set; }
	public List<ProductPurchase> PurchasedProducts { get; set; }

	public int Available()
	{
		int soldAndReserved = 0;
		PurchasedProducts.ForEach(p => soldAndReserved += p.ProductQuantity);
		ReservedProducts.FindAll(p => p.IsActive()).ForEach(p => soldAndReserved += p.Quantity);

		return Allocation - soldAndReserved;
	}

	public bool ReservationIdValid(Guid reservationId)
	{
		if (HasReservation(reservationId))
		{
			return GetReservationWith(reservationId).IsActive();
		}
		return false;
	}

	public ProductPurchase ConfirmPurchaseWith(Guid reservationId)
	{
		if (!ReservationIdValid(reservationId))
		{
			throw new Exception(string.Format("Cannot confirm the purchase with this Id: {0}", reservationId));
		}

		ProductReservation reservation = GetReservationWith(reservationId);
		ProductPurchase purchase = new ProductPurchase(this, reservation.Quantity);
		reservation.HasBeenConfirmed = true;
		PurchasedProducts.Add(purchase);
		return purchase;
	}

	public ProductReservation GetReservationWith(Guid reservationId)
	{
		if (!HasReservation(reservationId))
		{
			throw new Exception(string.Concat("No reservation found with id {0}", reservationId.ToString()));
		}
		return (from r in ReservedProducts where r.Id == reservationId select r).FirstOrDefault();
	}

	private bool HasReservation(Guid reservationId)
	{
		return ReservedProducts.Exists(p => p.Id == reservationId);
	}

	public bool CanReserveProduct(int quantity)
	{
		return Available() >= quantity;
	}

	public ProductReservation Reserve(int quantity)
	{
		if (!CanReserveProduct(quantity))
		{
			throw new Exception("Can not reserve this many tickets.");
		}

		ProductReservation reservation = new ProductReservation(this, 1, quantity);
		ReservedProducts.Add(reservation);
		return reservation;
	}
}

We maintain a list of purchased and reserved products in the corresponding List objects. The Allocation property means the initial number of products available for sale.

  • In Available() we check what’s left based on Allocation and the reserved and purchased tickets.
  • CanReserveProduct: we check if there are at least as many products available as the ‘quantity’ value
  • Reserve: we reserve a product and add it to the reservation list. We set the expiry date of the reservation to 1 minute. In reality this value should be higher of course but in the demo we don’t want to wait 30 minutes to test for the reservation being too old
  • HasReservation: check if the reservation exists in the reservation list using the reservation id
  • GetReservationWith: we retrieve the reservation based on its GUID
  • ReservationIdValid: check if a product can be purchased with the reservation id
  • ConfirmPurchaseWith: confirm the reservation by adding the constructed product to the purchase list

Here comes the ProductReservation class:

public class ProductReservation
{
	public ProductReservation(Product product, int expiryInMinutes, int quantity)
	{
		if (product == null) throw new ArgumentNullException("Product cannot be null.");
		if (quantity < 1) throw new ArgumentException("The quantity should be at least 1.");
		Product = product;
		Id = Guid.NewGuid();
		Expiry = DateTime.Now.AddMinutes(expiryInMinutes);
                Quantity = quantity;
	}

	public Guid Id { get; set; }
	public Product Product { get; set; }
	public DateTime Expiry { get; set; }
	public int Quantity { get; set; }
	public bool HasBeenConfirmed { get; set; }

	public bool Expired()
	{
		return DateTime.Now > Expiry;
	}

	public bool IsActive()
	{
		return !HasBeenConfirmed && !Expired();
	}
}

Note the expiry date property which fits in well with our discussion in the previous post: the client needs to reserve the product first and then confirm the purchase within the expiry date.

The ProductPurchase class is equally straightforward:

public class ProductPurchase
{
	public ProductPurchase(Product product, int quantity)
	{
		Id = Guid.NewGuid();
		if (product == null) throw new ArgumentNullException("Product cannot be null.");
		if (quantity < 1) throw new ArgumentException("The quantity should be at least 1.");
		Product = product;
		ProductQuantity = quantity;
	}

	public Guid Id { get; set; }
	public Product Product { get; set; }
	public int ProductQuantity { get; set; }
}

That’s all the domain logic we have in the model application. In the next post we’ll implement the repository.

View the list of posts on Architecture and Patterns here.

Advertisements

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

One Response to A model SOA application in .NET Part 2: the domain

  1. Pingback: Architecture and patterns | Michael's Excerpts

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

ultimatemindsettoday

A great WordPress.com site

Elliot Balynn's Blog

A directory of wonderful thoughts

Robin Sedlaczek's Blog

Developer on Microsoft Technologies

HarsH ReaLiTy

A Good Blog is Hard to Find

Softwarearchitektur in der Praxis

Wissenswertes zu Webentwicklung, Domain-Driven Design und Microservices

the software architecture

thoughts, ideas, diagrams,enterprise code, design pattern , solution designs

Technology Talks

on Microsoft technologies, Web, Android and others

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

Anything around ASP.NET MVC,WEB API, WCF, Entity Framework & AngularJS

Cyber Matters

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

Guru N Guns's

OneSolution To dOTnET.

Johnny Zraiby

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

%d bloggers like this: