3

Defining Service Boundaries by Splitting Entities

 3 years ago
source link: https://codeopinion.com/defining-service-boundaries-by-splitting-entities/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Defining Service Boundaries is a really important part of building a loosely coupled system, yet can often be difficult. Here’s one way of realizing where service boundaries lie but looking at Entities and the properties and how they relate.

YouTube

Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.

Catalog

For the purpose of this example, I’m going to use an e-commerce, distribution domain. Don’t worry too much if you don’t know distribution. At a high level, you buy products from vendors, store them in your warehouse, then sell them to customers.

Here’s what my Loosely Coupled Monolith demo application has as a solution structure.

Defining Service

There are 4 boundaries defined. Catalog, Purchasing, Sales, and Shipping.

In the Catalog project, we have a ProductModel that represents a product in our system.

using System;

namespace Catalog { public class ProductModel { public Guid Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } public decimal Cost { get; set; } public int Quantity { get; set; } } }

Dependency

Because we have this singular model of a product in our Catalog boundary, you can imagine the rest of the system will need access to it. In the Sales boundary, we have a feature to create an Order, which requires the Price of a product we’re purchasing.

For Sales to get the Product Price, we’ve exposed an interface & implementation to return product info.

using System; using System.Linq; using System.Threading.Tasks; using Catalog.Contracts; using Microsoft.EntityFrameworkCore;

namespace Catalog { public interface IProductQuery { Task<Product> GetProduct(Guid productId); }

public class Product { public Guid Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } } public class ProductQuery : IProductQuery { private readonly CatalogDbContext _dbContext;

public ProductQuery(CatalogDbContext dbContext) { _dbContext = dbContext; }

public async Task<Product> GetProduct(Guid productId) { return await _dbContext.Products .Where(x => x.Id == productId) .Select(x => new Product { Id = x.Id, Name = x.Name, Price = x.Price }) .SingleAsync(); } } }

Now here’s an example of using the IProductQuery.GetProduct() in our PurchaseProduct feature in our Sales boundary.

public class PurchaseProductHandler : IRequestHandler<PurchaseProduct> { private readonly SalesDbContext _dbContext; private readonly IProductQuery _productQuery;

public PurchaseProductHandler(SalesDbContext dbContext, IProductQuery productQuery) { _dbContext = dbContext; _productQuery = productQuery; }

public async Task<Unit> Handle(PurchaseProduct request, CancellationToken cancellationToken) { var product = await _productQuery.GetProduct(request.ProductId);

var order = new Order { OrderId = Guid.NewGuid(), Items = new List<OrderItem> { new OrderItem { ProductId = request.ProductId, Price = product.Price } } }; await _dbContext.Orders.AddAsync(order, cancellationToken); await _dbContext.SaveChangesAsync();

return Unit.Value; } }

Splitting Entities

Now, this might not seem like all that bad that Sales has to take a dependency on Catalog. But we want our boundaries to be as autonomous as possible.

The primary reason I think the above example occurs is that the notion of an entity must live only in one place. I call this the “Entity as a Service”. Where a particular boundary owns an entity and provides an API to manage that entity.

In my example in this post, the Catalog boundary owns the Product entity.

What’s better served, in my opinion, is to have each boundary own the behavior and data around the concept of an entity.

Sale Price

The Price property/field on our ProductModel has no bearing on any other property on that model. Meaning, the price does not affect the name of the product. The same goes for Cost and Quantity. If any of those properties were to change, it has no bearing on what the Price is.

If they have no bearing to each other, why are they in the same model?

In this case, Price on a Product actually belongs to the Sales boundary, not the Catalog.

We can have the same concept of a Product live in multiple boundaries.

using System;

namespace Sales { public class ProductModel { public Guid Id { get; set; } public decimal Price { get; set; } } }



using Microsoft.EntityFrameworkCore;

namespace Sales { public class SalesDbContext : DbContext { public DbSet<Order> Orders { get; set; } public DbSet<ProductModel> Products { get; set; } } }

The above example is the ProductModel I’ve created for the Sales Boundary and added it to our Entity Framework SalesDbContext.

Now the PurchaseProduct feature does not need a dependency on Catalog anymore.

public class PurchaseProductHandler : IRequestHandler<PurchaseProduct> { private readonly SalesDbContext _dbContext;

public PurchaseProductHandler(SalesDbContext dbContext) { _dbContext = dbContext; }

public async Task<Unit> Handle(PurchaseProduct request, CancellationToken cancellationToken) { var price = await _dbContext.Products .Where(x => x.Id == request.ProductId) .Select(x => x.Price) .SingleAsync();

var order = new Order { OrderId = Guid.NewGuid(), Items = new List<OrderItem> { new OrderItem { ProductId = request.ProductId, Price = price } } }; await _dbContext.Orders.AddAsync(order, cancellationToken); await _dbContext.SaveChangesAsync();

return Unit.Value; } }

Since we no longer need the Price in our Catalog ProductModel, I’ve removed it.

using System;

namespace Catalog { public class ProductModel { public Guid Id { get; set; } public string Name { get; set; } public decimal Cost { get; set; } public int Quantity { get; set; } } }

Now the Cost property is the next easy target. The likely same applies that our Purchasing boundary would be the owner of the Cost. It’s going to be managing this value with the Vendor/Manufacturer, so it would make sense that it would own the Cost of a Product.

Quantity

Quantity here is referring to the Quantity on Hand of the product in the warehouse. The logical next step would that the Quantity on Hand would be managed by an Inventory or Warehouse boundary.

Warehouses with physical goods can some times do what is called an Inventory Adjustment. This can happen for various reasons such as people actually counting the products, finding damaged product, etc. This is a way to update the system to reflect the actual quantity on hand.

What if we had business rule that stated that you cannot purchase a product if there is no quantity on hand?

How would you model this? Would Sales have to have a dependency on the Inventory/Warehouse context so it could get the latest Quantity on Hand of a product?

In the situations I’ve been in, they use a business function called Available to Promise. This is a value that is calculated by the quantity on hand in the warehouse, what has been purchased from vendors but not yet received, and what has been ordered.

Defining Service

With using asynchronous messaging/events, the Sales context would keep track of a products ATP value and use that for the business rule around when an Product can be ordered.

using System;

namespace Sales { public class ProductModel { public Guid Id { get; set; } public decimal Price { get; set; } public int AvailableToPromise { get; set; } } }

Defining Service Boundaries

Defining service boundaries is difficult. Start out by looking at your Entities and splitting them up by sharing the same concept of a Entity across multiple boundaries.

An Entity does NOT need to reside in one single boundary. As shown in this example, the concept of a Product can reside in many different boundaries, and each concept owning the data/behavior it owns.

Follow @CodeOpinion on Twitter

Leave this field empty if you're human:

Links


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK