我非常喜欢 NTiers 的开发选择,当然它并不适合所有场景。

我目前正在开发一个新项目,我正在尝试按照我通常的工作方式进行游戏,并尝试看看是否可以清理它。因为我是一个非常坏的孩子,并且在表示层中放置了太多代码。

我正常的业务层结构是这样的(基本视图):

  • 商业
    • 服务
      • Foo组件
        • Foo助手
        • Foo工作流程
      • Bah组件
        • 巴帮助者
        • Bah工作流程
    • 公用事业
      • 常见的
      • 异常处理程序
      • 进口商
      • ETC...

现在有了上面的内容,我可以通过各自的助手直接保存 Foo 对象和 Bah 对象。

XXXHelpers 使我能够访问保存、编辑和加载相应的对象,但是我应该将逻辑放在哪里来保存带有子对象的对象。

例如:

我们有以下对象(我知道不是很好的对象)

  • 员工
  • 员工详情
  • 员工会员
  • 员工档案

目前,我将在表示层中构建这些所有内容,然后将它们传递给他们的助手,我觉得这是错误的,我认为数据应该传递到业务层中某个地方的表示上方的单个点并在那里进行整理。

但我有点不知道该把这个逻辑放在哪里,以及如何称呼该部门,它会在“公用事业”下作为“EmployeeManager”还是类似的东西?

你会怎么办?我知道这都是偏好。

更详细的布局

工作流程包含直接对 DataRepository 的所有调用,例如:

public ObjectNameGetById(Guid id)
{
    return DataRepository.ObjectNameProvider.GetById(id);
}

然后帮助程序提供者访问工作流程:

public ObjectName GetById(Guid id)
{
    return loadWorkflow.GetById(id);
}

这是为了减少重复的代码,因为您可以在工作流程中打个电话,以供GetBysomeProperty,然后在助手中进行几个电话,这些电话可以进行其他操作并以不同的方式返回数据,一个糟糕的例子将是公开的GetByIdasc和getByidDescc

通过使用 DataRepository 分离对数据模型的调用,这意味着可以将模型替换为另一个实例(这就是想法),但 ProviderHelper 尚未分解,因此它不可互换,因为它是不幸的是,硬编码为 EF。我不打算改变访问技术,但将来可能会有更好的东西,或者只是所有酷孩子现在都在使用的东西,而我可能想实现。

项目名称.Core

projectName.Business
    - Interfaces
        - IDeleteWorkflows.cs
        - ILoadWorkflows.cs
        - ISaveWorkflows.cs
        - IServiceHelper.cs
        - IServiceViewHelper.cs
    - Services
        - ObjectNameComponent
            - Helpers
                - ObjectNameHelper.cs
            - Workflows
                - DeleteObjectNameWorkflow.cs
                - LoadObjectNameWorkflow.cs
                - SaveObjectNameWorkflow.cs
    - Utilities
        - Common
            - SettingsManager.cs
            - JavascriptManager.cs
            - XmlHelper.cs
            - others...

        - ExceptionHandlers
            - ExceptionManager.cs
            - ExceptionManagerFactory.cs
            - ExceptionNotifier.cs


projectName.Data
    - Bases
        - ObjectNameProviderBase.cs
    - Helpers
        - ProviderHelper.cs
    - Interfaces
        - IProviderBase.cs
    - DataRepository.cs

projectName.Data.Model
    - Database.edmx

projectName.Entities (Entities that represent the DB tables are created by EF in .Data.Model, this is for others that I may need that are not related to the database)
    - Helpers
        - EnumHelper.cs

项目名称.演示文稿

(取决于应用程序的调用是什么)

projectName.web
projectName.mvc
projectName.admin

测试项目

projectName.Business.Tests
projectName.Data.Test
有帮助吗?

解决方案

对于一个有趣的问题+1。

因此,您描述的问题非常常见 - 我会采取不同的方法 - 首先使用逻辑层,其次使用实用程序和帮助程序命名空间,我会尝试完全排除它们 - 我会告诉你为什么第二。

但首先,我在这里首选的方法是非常常见的企业架构,我将尝试简要强调它,但还有更多的深度。它确实需要在思维上进行一些彻底的改变——使用 NHibernate 或 Entity 框架来允许您直接查询对象模型,并让 ORM 处理诸如映射到数据库和从数据库映射以及延迟加载关系等事情。这样做将使您能够在域模型中实现所有业务逻辑。

首先是层(或解决方案中的项目);

您的应用程序域

领域模型 - 代表问题空间的对象。这些是普通的旧 CLR 对象,包含所有关键业务逻辑。这是示例对象所在的位置,它们的关系将表示为集合。该层中没有任何内容涉及持久性等,它只是对象。

您的应用程序数据

存储库类 - 这些类用于处理获取域模型的聚合根。

例如,在您的示例类中,您不太可能希望在不查看 Employee 的情况下查看 EmployeeDetails (这是我知道的假设,但您明白要点 - 发票行是一个更好的示例,您通常会通过发票而不是单独加载)。因此,存储库类(每个聚合根有一个类)将负责使用相关 ORM 从数据库中获取初始实体,实现任何查询策略(如分页或排序)并将聚合根返回到消费者。存储库将使用当前活动的数据上下文(NHibernate 中的 ISession) - 如何创建此会话取决于您正在构建的应用程序类型。

您的应用程序.工作流程

  • 也可以称为 YourApplication.Services,但这可能与 Web 服务混淆
  • 这一层都是关于相互关联的、复杂的原子操作——而不是在表示层中调用一堆东西,从而增加耦合,您可以将这些操作包装到工作流或服务中。
  • 在许多应用程序中您可以不用它。

其他层取决于您的架构和您正在实现的应用程序。

您的应用程序.您选择的演示层

如果您使用 Web 服务来分发层,那么您将创建仅代表您在域和消费者之间公开的数据的 DTO 合约。您将定义知道如何将数据从域移入和移出这些合约的汇编器(您永远不会通过网络发送域对象!)

在这种情况下,并且您还要创建客户端,您将使用上面在表示层中定义的操作和数据协定,可能直接绑定到 DTO,因为每个 DTO 都应该是特定于视图的。

如果您不需要分发层,请记住分布式架构的第一条规则是不分发,那么您将直接从 asp.net、mvc、wpf、winforms 等内部使用工作流/服务和存储库。

剩下的就是建立数据上下文的地方。在 Web 应用程序中,每个请求通常都是相当独立的,因此请求范围的上下文是最好的。这意味着上下文和连接是在请求开始时建立并在请求结束时处理的。让您选择的 IoC/依赖注入框架为您配置每个请求的组件是很简单的。

在桌面应用程序、WPF 或 winforms 中,每个表单都有一个上下文。这确保了在编辑对话框中对域实体进行的编辑会更新模型,但不会将其写入数据库(例如:选择取消)不要干扰其他上下文,或者更糟糕的是最终被意外保留。

依赖注入

以上所有内容都将首先定义为接口,然后通过 IoC 和依赖注入框架实现具体实现(我更喜欢温莎城堡)。这允许您独立地隔离、模拟和单元测试各个层,并且在大型应用程序中,依赖项注入是一个救星!

那些命名空间

最后,我失去助手命名空间的原因是,在上面的模型中,您不需要它们,而且,就像实用程序命名空间一样,它们给懒惰的开发人员一个借口,让他们不考虑一段代码在逻辑上的位置。MyApp.Helpers.* 和 MyApp.Utility.* 只是意味着,如果我有一些代码,比如说一个逻辑上可能属于 MyApp.Data.Repositories.Customers 的异常处理程序(也许它是一个客户引用,不是唯一的异常),一个懒惰的异常处理程序开发人员可以将其放入 MyApp.Utility.CustomerRefNotUniqueException 中,而无需真正思考。

如果您有需要包装的通用框架类型代码,请添加 MyApp.Framework 项目和相关命名空间。如果您要添加新的模型绑定器,请将其放入 MyApp.Framework.Mvc,如果是常见的日志记录功能,请将其放入 MyApp.Framework.Logging 等。在大多数情况下,不需要引入实用程序或帮助程序命名空间。

包起来

所以这只是表面的内容 - 希望它能有所帮助。这就是我今天开发软件的方式,并且我故意尽量简短 - 如果我可以详细说明任何细节,请告诉我。关于这篇固执己见的文章的最后要说的是,以上内容是针对相当大规模的开发 - 如果您正在编写记事本版本 2 或公司电话簿,以上内容可能完全是多余的!

欢呼托尼

其他提示

此页面上有一个关于应用程序布局的漂亮图表和描述,尽管进一步查看文章,应用程序并未拆分为物理层(单独的项目) - 实体框架 POCO 存储库

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top