SOA问题:公开实体
我想在我的3层结构中加入SOA模式。 我在BLL和UI之间创建了一个服务层(WCF主机)。 我的结构设置现在看起来像这样
UI WCF BLL DAL
问题是,我将我的实体放在单独的DLL中(并且它在除了UI之外的所有层中都可见)现在,我需要公开它以便我的服务的使用者可以使用它。在这种情况下,UI。 我怎么可能这样做?
Entities.DLL
namespace Entities { public class Account { public string AcctID { get; set; } public string AcctName { get; set; } } }
现在,我打算在WCF中使用它
服务接口层
public class AccountService : IAccountService { public Account GetAccount(string AcctID) { //fetch from DAL through BLL } }
只是属性我的实体可以吗? (注意,我也在使用DAL和BLL中的实体)
using System.Runtime.Serialization; namespace Entities { [DataContract] public class Account { [DataMember] public string AcctID { get; set; } [DataMember] public string AcctName { get; set; } } }
有什么建议吗?
这是适合我们的系统:
您通常应该使用数据传输对象来反映客户端期望的数据。 业务层应该定义这些DTO及其存储库接口。 数据层应实现存储库接口,将数据层实体转换为DTO。 WCF层应该只是您的各种存储库接口方法的外向包装器。
这样,它看起来更像是这样的:
UI --- | BLL - DAL WCF --- / [DTO] [库] [实体]
在我看来,我认为WCF层是UI层的一部分,所以我觉得让他们都知道业务层中定义的对象。 但是,您可以更进一步,并使WCF层负责将业务对象转换为DTO:
UI - WCF - BLL - DAL [DTO] [知识库] [Business Objects] [实体]
这样,每个层最多只能识别其每一侧的单个层。 DTO可以注释为序列化或其他任何东西,因为它们实际上仅用于此目的。 只有数据访问层才能识别您的数据实体。
在回复您的评论时:
如果您的实体是在数据访问层中定义的,那么它们实际上不是DTO。 他们正在为您的数据层建模,这不一定直接转换为UI中您需要的对象。
我建议为您的存储库定义接口的原因是您可以使用dependency injection来放松WCF层和业务层之间的耦合。 这也将使您的WCF层可unit testing,因为您可以创建模拟特定情况的假或模拟存储库实现。
在我推荐的第一个模型中,您的WCF方法看起来几乎与您的存储库方法完全相同,因此典型的WCF基本上只是“包装”存储库方法:
public IEnumerable GetActiveTasks() { return _taskRepository.GetActiveTasksForUser(_sessionManager.CurrentUser); }
我可以告诉你我是怎么做到的。
我有单独的DTO和实体 – 并不总是1:1的关系。 我真的不喜欢在我的实体中拥有所有属性。 (此外,它打破了封装,因为所有属性都是突然读写的。
如果你想在两者之间轻松转换 – 有一些库可以让它像AutoMapper一样容易(ier)。
如果您将实体用作DTO,您通常会发送太多数据 – 例如,具有多个类型为Order的OpenOrders的帐户的订单。 每当您获取一个订单时,您也将获得该帐户的所有未结订单。
其次 – 我会在UI上使用与在服务层中使用相同的business-dll – 因此我可以在将其发送到服务器之前在客户端进行validation。 这部分当然是可选的 – 你也可以复制逻辑(但我也讨厌重复:-))。
希望这能为你带来一些方向。
我想我使用了AutoMapper。
我最后通过WCF将实体暴露为DTO ..简单的方法..
实体
namespace Entities { public class Account { public string AccountNo { get; set; } public string AccountName { get; set; } } }
BLL
namespace BLL { // This defines the repository interface. public interface IAccountRepository { Account GetAccount(int accountId); } public class AccountRepository { public Account GetAccount(int accountId) { // get the Account object from the data layer. } } // Using an interface makes it easy to swap various implementations. // The implementation above would be the one you'd normally use, but you could // create others like this to help with unit testing and such. public class FakeAccountRepository : IAccountRepository { public Account GetAccount(int accountId) { return new Account { AccountName = "JuanDeLaCruz", AccountNo = "123456" }; } } }
WCF
[ServiceContract] public interface IService { [OperationContract] AccountDTO GetAccount(int accountId); } [DataContract] public class AccountDTO { [DataMember] public string AccountNo { get; set; } [DataMember] public string AccountName { get; set; } } public class Service : IService { // Define a Factory in your .svc file to inject a repository implementation. // It's best to use an IoC container like Ninject for this sort of thing. public Service( // no pun intended IAccountRepository accountRepository) { _accountRepository = accountRepository } public AccountDTO GetAccount(int accountId) { Mapper.CreateMap(); var account = _accountRepository.GetAccount(accountId); var accountDto = Mapper.Map(account); return account; } }
WCF Aspx消费者
protected void Page_Load(object sender, EventArgs e) { ServiceClient accountService= new ServiceClient(); AccountDTO account = accountService.GetAccount(); Response.Write(account.AccountName); }
请评论任何建议/更正人.. ^^
感谢斯蒂芬林爵士和哥布林
DTO是非常好的方法,在某些情况下它们是绝对必要的。 其中一些情况是:
- 大项目 – 与其他场景一起使用。 分离关注很重要。
- 实体是域对象=包含业务逻辑
- 实体以某种方式特定于.NET,并且必须从其他平台使用服务
- 服务层为每个操作公开专用数据类型,而不是CRUDy接口。 例如,用于选择的操作可以返回具有诸如创建日期,最后修改日期等数据的对象。但是用于更新的操作不需要从客户端传输该数据,因此它使用DTO而不是这些字段。 你通常走得更远,你没有纯粹的选择和更新,但有一些真正的业务function。
另一方面,您的架构应该由您的需求和应用程序的预期复杂性和大小驱动。 DTO涉及许多额外工作=额外费用。 对于较小的简单项目,其中您的服务仅由您使用.NET编写的UI客户端使用,在单独的程序集中定义您的实体没有任何错误,标记这些实体具有用于序列化和数据注释的属性(可用于两者的validation)客户端和服务器端)或其他validation属性(例如来自企业库的validation应用程序块)并在包括UI客户端的所有应用程序层之间共享此程序集。 简单第一。
如果您想在客户端公开服务合同,最简单的方法就是这样
[Serializable] public class Accountd { public string AccountNo { get; set; } public string AccountName { get; set; } }
如果您需要更多控制哪个成员应该越过边界,那么使用它
[DataContract] public class Account { [DataMember] public string AccountNo { get; set; } public string AccountName { get; set; } **// client won't see this data.** }