WCF 示例 – 第一章 – 基线






4.83/5 (35投票s)
模型、存储库和服务的初稿版本。
![]() |
![]() |
|
引言 | 第二章 |
系列文章
WCF 示例系列文章描述了如何使用 WCF 进行通信和 NHibernate 进行持久化来设计和开发 WPF 客户端。系列介绍描述了文章和项目的范围。
章节概述
这是本系列的第一章。本章为 eDirectory 解决方案奠定了基础;我们将介绍一些核心解决方案概念,例如 Customer
实体、存储库的定义、客户服务以及我们的最初几个测试。在后面的章节中,我们将讨论如何演进本文中描述的原始设计,以及如何简化持久化组件,以便在需要额外的实体时,在使解决方案的这一部分正常工作方面付出很少的努力。
eDirectory 解决方案的最新代码可在 CodePlex 上找到。
模型 - 客户实体
正如我们在介绍中提到的,我们将使模型非常简单,目前只需要一个实体。customer
类声明如下:
public class Customer
{
protected Customer() { }
public long Id { get; private set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
public string Telephone { get; private set; }
}
需要注意的几点:属性是 protected
,因此只能通过操作方法更改它们;该类不提供 public
构造函数,通过这种方式,我们努力设置类,使其状态只能通过实体内声明的众所周知的方法进行更改。我们不应该假设我们的类将以正确的方式使用,因此将构造函数设置为 protected
提供了一种很好的方式来控制新实例的创建。当我们在 第二章 - 存储库 中讨论持久化时,我们将证明这种方法是合理的。
此外,实体在 eDirectory.Domain
程序集中定义,该程序集将仅由我们的服务器端组件使用;但是,客户端不使用域类,而是向客户端提供 DTO。
客户服务
eDirectory 提供以下客户服务:
函数 | 描述 |
GetById |
通过提供唯一 ID 检索客户 |
ListAll |
返回所有客户实例 |
CreateNewCustomer |
创建新的客户实例 |
UpdateCustomer |
修改现有客户实例 |
服务使用以下约定定义:
- 输入参数将是基元或数据传输对象
- 所有服务方法都是函数,也就是说,它们都向客户端返回一些最基本的信息。
此外,服务契约(接口)在公共程序集中声明,因为服务器和客户端组件都需要它们,我们不会在客户端使用 Web References 向导功能在客户端生成 WCF 代理。对于 DTO 也是如此,它们在服务器和客户端组件之间共享。
在此阶段,我们需要我们的第一个 DTO,它与我们的 Customer
实体非常相似:
public class CustomerDto
{
public long CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Telephone { get; set; }
}
现在,我们可以定义我们的第一个服务契约:
[ServiceContract(Namespace = "http://wcfbyexample/customerservices/")]
public interface ICustomerService
{
[OperationContract]
CustomerDto CreateNewCustomer(CustomerDto customer);
[OperationContract]
CustomerDto GetById(long id);
[OperationContract]
CustomerDto UpdateCustomer(CustomerDto customer);
[OperationContract]
List<customerdto> FindAll();
}
让我们尝试创建一个创建客户实例的机制。
存储库
领域实体应该与持久化后端机制解耦。存储库模式为我们的领域实体提供所需的持久化功能,从而实现解耦。
此时,以下存储库定义就足够了:
public interface IRepository<TEntity>
{
#region CRUD operations
TEntity Save(TEntity instance);
void Update(TEntity instance);
void Remove(TEntity instance);
#endregion
#region Retrieval Operations
TEntity GetById(long id);
IQueryable<TEntity> FindAll();
#endregion
}
最重要的一点是,我们定义了一个泛型存储库,并且 FindAll
方法返回一个 IQueryable
实例。
创建工厂方法
此时,我们可以回到我们的领域类,并提供一种机制来促进客户实例的创建:
public static Customer Create(IRepository<customer> repository, CustomerDto operation)
{
var instance = new Customer
{
FirstName = operation.FirstName,
LastName = operation.LastName,
Telephone = operation.Telephone
};
repository.Save(instance);
return instance;
}
请注意 DTO 实例如何用于填充新客户实例的属性。然后针对传入的存储库执行 save
方法。我们将在实体方法和工厂中传递存储库实例,而不是在实体中使用对存储库实现的引用。随着我们涵盖应用程序的未来方面,这种方法将不断演进。这里需要注意的另一个方面是,工厂方法创建持久化的客户实例,因此当客户实例返回时,持久化上下文完全了解新实例,实际上在调用 save 方法后 ID 就可用了。
内存存储库实现
此时,我们可以开始进行一些 TDD,我们可以模拟存储库等等,但是提供一个内存存储库实现相对来说成本不高,并且可以促进测试和业务探索。由于内存持久化器永远不会在生产环境中使用,我们希望在不同的程序集 eDirectory.Naive
中声明此实现。我们为内存持久化器定义一个具有通用功能的抽象类:
public abstract class RepositoryEntityStore<TEntity>
:IRepository<TEntity>
{
protected readonly IDictionary<long, TEntity> RepositoryMap = new Dictionary<long, TEntity>();
#region IRepository<TEntity> Members
public abstract TEntity Save(TEntity instance);
public abstract void Update(TEntity instance);
public abstract void Remove(TEntity instance);
public TEntity GetById(long id)
{
return RepositoryMap[id];
}
public IQueryable<TEntity> FindAll()
{
return RepositoryMap.Values.AsQueryable();
}
#endregion
}
然后我们创建 Customer
实现:
public class RepositoryCustomer
: RepositoryEntityStore<customer>
{
public override Customer Save(Customer instance)
{
...
}
public override void Update(Customer instance)
{
...
}
public override void Remove(Customer instance)
{
...
}
private void GetNewId(Customer instance)
{
...
}
private readonly IDictionary<type,> Setters = new Dictionary<type,>();
private MethodInfo GetSetter(Type type)
{
...
}
}
上述代码还有很大的改进空间,但值得注意的是实体如何使用实体 ID 存储在哈希映射中。正如我在系列介绍中提到的,我们对数据库设计拥有完全控制权,因此我们选择在所有表中都有一个数字主键,这简化了领域实体的定义。在下一章:第二章 - 存储库 中,我们将看到上述代码如何演变,并且只需要一个适用于所有实体类型的实现。
我们的第一个测试
让我们使用工厂创建一个客户,然后检查 ID 和名字是否正确:
[TestClass]
public class CustomerTests
{
[TestMethod]
public void CreateCustomer()
{
// need to create an instance of the repository
var repository = new RepositoryCustomer();
var dto = new CustomerDto
{
FirstName = "Joe",
LastName = "Bloggs",
Telephone = "9999-8888"
};
var customer = Customer.Create(repository, dto);
Assert.IsFalse(customer.Id == 0, "Customer Id should have been updated");
Assert.AreSame(customer.FirstName, dto.FirstName, "First Name are different");
}
}
客户服务实现
我们现在准备创建我们的第一个服务实现:
public class CustomerService
:ICustomerService
{
public IRepository<customer> Repository { get; set; }
#region ICustomerService Members
public Common.Dto.Customer.CustomerDto CreateNewCustomer(CustomerDto dto)
{
var customer = Customer.Create(Repository, dto);
return new CustomerDto
{
CustomerId = customer.Id,
FirstName = customer.FirstName,
LastName = customer.LastName,
Telephone = customer.Telephone
};
}
...
}
让我们创建一个执行此服务的测试:
[TestClass]
public class CustomerServiceTests
{
[TestMethod]
public void CreateCustomer()
{
// need to create an instance of the repository
var service = new CustomerService { Repository = new RepositoryCustomer() };
var dto = new CustomerDto
{
FirstName = "Joe",
LastName = "Bloggs",
Telephone = "9999-8888"
};
var customer = service.CreateNewCustomer(dto);
Assert.IsFalse(customer.CustomerId == 0,
"Customer Id should have been updated");
Assert.AreSame(customer.FirstName, dto.FirstName,
"First Name are different");
}
}
章节总结
我们在这篇文章中涵盖了许多方面。首先,我们设置了几个项目:Common、Domain 和 Naive。我们描述了设计领域实体及其与存储库关系的基本知识。我们通过使用服务创建客户实例并编写最初几个测试来结束本章。
在下一章中,我们将讨论到目前为止使用的一些建议模式,我们将识别一些可以改进的方面,并将引入存储库定位器概念。