3

对于我的开发选择,我是 NTiers 的忠实粉丝,当然它并不适合所有场景。

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

我正常的业务层结构是这样的(它的基本观点):

  • 商业
    • 服务
      • FooComponent
        • FooHelpers
        • FooWorkflows
      • Bah组件
        • BahHelpers
        • Bah工作流
    • 实用程序
      • 常见的
      • 异常处理程序
      • 进口商
      • ETC...

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

XXXHelpers 让我可以访问保存、编辑和加载相应的对象,但是我应该将逻辑与子对象一起保存在哪里。

例如:

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

  • 员工
  • 员工详情
  • 员工会员
  • 员工简介

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

但是我有点不知所措,我将把这个逻辑放在哪里以及如何称呼这个部门,它会作为 EmployeeManager 或类似的东西归入 Utilities 下吗?

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

更详细的布局

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

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

然后助手提供对工作流的访问:

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

这是为了减少重复代码,因为您可以在工作流中调用一次 getBySomeProperty,然后在 Helper 中调用几次,这些调用可以执行其他操作并以不同方式返回数据,一个不好的例子是 public GetByIdAsc 和 GetByIdDesc

通过使用 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
4

2 回答 2

3

+1 提出一个有趣的问题。

所以,你描述的问题很常见——我会采取不同的方法——首先是逻辑层,其次是实用程序和帮助程序命名空间,我会尝试完全解决这些问题——我会告诉你为什么第二。

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

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

你的应用程序.域

领域模型 - 代表您的问题空间的对象。这些是带有所有关键业务逻辑的普通旧 CLR 对象。这是您的示例对象所在的位置,它们的关系将表示为集合。这一层没有任何东西处理持久性等,它只是对象。

你的应用程序数据

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

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

YourApplication.Workflow

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

然后其他层取决于您的体系结构和您正在实施的应用程序。

YourApplication.YourChosenPresentationTier

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

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

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

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

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

依赖注入

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

那些命名空间

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

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

包起来

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

干杯托尼

于 2010-08-25T22:24:42.900 回答
0

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

于 2010-08-31T12:15:36.807 回答