业务层结构,你如何构建你的?

发布于 2024-09-16 12:46:45 字数 2928 浏览 2 评论 0原文

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

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

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

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

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

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

例如:

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

  • Employee
  • EmployeeDetails
  • EmployeeMembership
  • EmployeeProfile

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

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

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

更详细的布局

工作流包含直接对 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 的。 我不打算改变访问技术,但将来可能会有更好的东西,或者只是所有酷孩子现在都在使用的东西,而我可能想实现。

projectName.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.Presenation

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

projectName.web
projectName.mvc
projectName.admin

测试项目

projectName.Business.Tests
projectName.Data.Test

I am a big fan of NTiers for my development choices, of course it doesnt fit every scenario.

I am currently working on a new project and I am trying to have a play with the way I normally work, and trying to see if I can clean it up. As I have been a very bad boy and have been putting too much code in the presentation layer.

My normal business layer structure is this (basic view of it):

  • Business
    • Services
      • FooComponent
        • FooHelpers
        • FooWorkflows
      • BahComponent
        • BahHelpers
        • BahWorkflows
    • Utilities
      • Common
      • ExceptionHandlers
      • Importers
      • etc...

Now with the above I have great access to directly save a Foo object and a Bah object, via their respective helpers.

The XXXHelpers give me access to Save, Edit and Load the respective objects, but where do I put the logic to save objects with child objects.

For example:

We have the below objects (not very good objects I know)

  • Employee
  • EmployeeDetails
  • EmployeeMembership
  • EmployeeProfile

Currently I would build these all up in the presentation layer and then pass them to their Helpers, I feel this is wrong, I think the data should be passed to a single point above presentation in the business layer some place and sorted out there.

But I'm at a bit of a loss as to where I would put this logic and what to call the sector, would it go under Utilities as EmployeeManager or something like this?

What would you do? and I know this is all preference.

A more detailed layout

The workflows contain all the calls directly to the DataRepository for example:

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

And then the helpers provider access to the workflows:

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

This is to cut down on duplicate code, as you can have one call in the workflow to getBySomeProperty
and then several calls in the Helper which could do other operations and return the data in different ways, a bad example would be public GetByIdAsc and GetByIdDesc

By seperating the calls to the Data Model by using the DataRepository, it means that it would be possible to swap out the model for another instance (that was the thinking) but ProviderHelper has not been broken down so it is not interchangable, as it is hardcode to EF unfortunately.
I dont intend to change the access technology, but in the future there might be something better or just something that all the cool kids are now using that I might want to implement instead.

projectName.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.Presenation

(depends what the call of the application is)

projectName.web
projectName.mvc
projectName.admin

The test Projects

projectName.Business.Tests
projectName.Data.Test

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

じ违心 2024-09-23 12:46:45

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

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

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

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

YourApplication.Domain

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

YourApplication.Data

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

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

YourApplication.Workflow

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

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

YourApplication.YourChosenPresentationTier

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

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

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

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

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

依赖注入

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

那些命名空间

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

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

总结

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

干杯
托尼

+1 for an interesting question.

So, the problem you describe is pretty common - I'd take a different approach - first with the logical tiers and secondly with the utility and helper namespaces, which I'd try and factor out completely - I'll tell you why in a second.

But first, my preferred approach here is pretty common enterprise architecture which I'll try to highlight in brief, but there's much more depth out there. It does require some radical changes in thinking - using NHibernate or Entity framework to allow you to query your object model directly and let the ORM deal with things like mapping to and from the database and lazy loading relationships etc. Doing this will allow you to implement all of your business logic within a domain model.

First the tiers (or projects in your solution);

YourApplication.Domain

The domain model - the objects representing your problem space. These are plain old CLR objects with all of your key business logic. This is where your example objects would live, and their relationships would be represented as collections. There is nothing in this layer that deals with persistence etc, it's just objects.

YourApplication.Data

Repository classes - these are classes that deal with getting the aggregate root(s) of your domain model.

For instance, it's unlikely in your sample classes that you would want to look at EmployeeDetails without also looking at Employee (an assumption I know, but you get the gist - invoice lines is a better example, you generally will get to invoice lines via an invoice rather than loading them independently). As such, the repository classes, of which you have one class per aggregate root will be responsible for getting initial entities out of the database using the ORM in question, implementing any query strategies (like paging or sorting) and returning the aggregate root to the consumer. The repository would consume the current active data context (ISession in NHibernate) - how this session is created depends on what type of app you are building.

YourApplication.Workflow

  • Could also be called YourApplication.Services, but this can be confused with web services
  • This tier is all about interrelated, complex atomic operations - rather than have a bunch of things to be called in your presentation tier, and therefore increase coupling, you can wrap such operations into workflows or services.
  • It's possible you could do without this in many applications.

Other tiers then depend on your architecture and the application you're implementing.

YourApplication.YourChosenPresentationTier

If you're using web services to distribute your tiers, then you would create DTO contracts that represent just the data you are exposing between the domain and the consumers. You would define assemblers that would know how to move data in and out of these contracts from the domain (you would never send domain objects over the wire!)

In this situation, and you're also creating the client, you would consume the operation and data contracts defined above in your presentation tier, probably binding to the DTOs directly as each DTO should be view specific.

If you have no need to distribute your tiers, remembering the first rule of distributed architectures is don't distribute, then you would consume the workflow/services and repositories directly from within asp.net, mvc, wpf, winforms etc.

That just leaves where the data contexts are established. In a web application, each request is usually pretty self contained, so a request scoped context is best. That means that the context and connection is established at the start of the request and disposed at the end. It's trivial to get your chosen IoC/dependency injection framework to configure per-request components for you.

In a desktop app, WPF or winforms, you would have a context per form. This ensures that edits to domain entities in an edit dialog that update the model but don't make it to the database (eg: Cancel was selected) don't interfere with other contexts or worse end up being accidentally persisted.

Dependency injection

All of the above would be defined as interfaces first, with concrete implementations realised through an IoC and dependency injection framework (my preference is castle windsor). This allows you to isolate, mock and unit test individual tiers independently and in a large application, dependency injection is a life saver!

Those namespaces

Finally, the reason I'd lose the helpers namespace is, in the model above, you don't need them, but also, like utility namespaces they give lazy developers an excuse not to think about where a piece of code logically sits. MyApp.Helpers.* and MyApp.Utility.* just means that if I have some code, say an exception handler that maybe logically belongs within MyApp.Data.Repositories.Customers (maybe it's a customer ref is not unique exception), a lazy developer can just place it in MyApp.Utility.CustomerRefNotUniqueException without really having to think.

If you have common framework type code that you need to wrap up, add a MyApp.Framework project and relevant namespaces. If your're adding a new model binder, put it in MyApp.Framework.Mvc, if it's common logging functionality, put it in MyApp.Framework.Logging and so on. In most cases, there shouldn't be any need to introduce a utility or helpers namespace.

Wrap up

So that scratches the surface - hope it's of some help. This is how I'm developing software today, and I've intentionally tried to be brief - if I can elaborate on any specifics, let me know. The final thing to say on this opinionated piece is the above is for reasonably large scale development - if you're writing notepad version 2 or a corporate phone book, the above is probably total overkill!!!

Cheers
Tony

迎风吟唱 2024-09-23 12:46:45

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

There is a nice diagram and a description on this page about the application layout, alhtough looks further down the article the application isnt split into physical layers (seperate project) - Entity Framework POCO Repository

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文