如何在维护领域驱动设计架构的同时使用 WCF 服务设置 Ninject?

发布于 2024-12-12 05:57:23 字数 3781 浏览 0 评论 0原文

我正在尝试实现一个概念证明,其中我编写了一个遵循领域驱动设计准则的模块(假设是论坛),并且将具有可插入存储库,并且整个模块将可在本地 Web 服务器中插入(本地托管 dll)或通过 WCF 服务。

遵循领域驱动设计指南,我将拥有一组如下编写的业务对象:

    public class Forum
    {
        readonly IRepository _forumRepository;
        public Forum(IRepository forumRepository)
        {
            _forumRepository = forumRepository;
        }

        public string Name { get; set; }

        public void Save()
        {
            _forumRepository.SaveForum(this);
        }
    }

假设 Web 应用程序需要创建一个新论坛。

如果模块通过 dll 文件在本地托管,那么这一切都很好。 Web 应用程序代码只需使用 Ninject(或任何 DI)来实例化业务对象并调用 Save 方法。

但是,如果实施者想要在两者之间引入服务层怎么办?假设他们想要在应用程序和数据库层之间引入 WCF 服务层,因为他们希望物理架构成为 Web 服务器 -> Web 服务器。应用服务器->数据库服务器。显然,论坛模块 dll 将由 Web 服务器和应用程序服务器托管。但是,该模块不再可用。

如果 Web 服务器通过存储库注入实例化对象并将其传递到 WCF 服务层,则即使我允许序列化业务对象,_forumRepository 字段也会丢失。

无论如何,实现者应该能够让应用程序服务器选择存储库。那么,有了这个需求,实现者在从Web服务器接收到已经实例化的对象后,如何在应用服务器端注入存储库呢?有没有办法告诉 WCF 服务在反序列化过程中实例化对象时注入存储库?

我读了这篇文章如何将依赖注入 (Ninject) 与 WCF 服务结合使用,并查看了示例解决方案,但这仅演示了如何将存储库直接注入到服务中。它似乎并没有解决我在这里讨论的问题。

这是一个示例,说明如果编写正确,我将如何预见代码。正如我之前所说,这里的主要问题是我不知道如何将存储库注入到服务端的域对象中。

应用程序代码(Web 应用程序或 Windows 应用程序):

Forum newForum = new Forum();
//Set some properties on newForum.        
IKernel kernel = new StandardKernel();  //Instantiate ninject kernel.
//Yes, I know using the using statement means that IService needs to derive from IDisposable.  A local service would just have an empty implementation for IDisposable since there would be nothing to clean up and this would allow different service architectures to be plugged into the application.
using (IForumService forumService = kernel.Get<IForumService>())  //Get concrete implementation bound to IForumService via ninject.
{
    //Send forum to service layer for local OR WCF processing (on an app server)
    forumService.SaveForum(newForum);
}

服务代码(这就是问题):

public class WCFForumService : IForumService
{
    public void SaveForum(Forum forum)
    {
        //PROBLEM SCENARIO:  How do I now inject the repository I want to use since I already have an instance of forum?  
        //Can I make WCF inject the repository when it is deserializing the forum object before passing it into this method?
        forum.Save();
    }
}

如果可能的话,我宁愿不强迫论坛模块的实现者为每个域对象创建 DTO。这违反了 DRY 原则。如果我可以创建域对象,这样它们就足够通用和/或可扩展,可以毫不费力地用作 DTO 本身,那就太好了。这样,我可以使用数据注释对域对象进行业务规则验证,并且实现者可以在 Web 窗体、MVC3 或 Silverlight 项目中使用这些对象,而无需手动重新进行所有验证。

编辑:以上是完全错误的方法。编辑以展示更有效的方法。

public class Forum
{
    public Forum(string name)
    {
        Name = name;
    }

    public string Name { get; set; }
}

public interface IForumRepository : IRepository<Forum>
{
    void Add(Forum forum);
    Forum this[object id] { get; set; }
}

//Client Code (Called from the WCF service hosting the domain)
public class WCFAppService
{
    public void SaveForum(ForumDTO forumInfo)
    {
        IKernel kernel = StandardKernel();
        IForumRepository repository = kernel.Get<IForumRepository>();
        Forum forum = repository[forumInfo.ID];
        if (forum != null)
        {
            repository[forumInfo.ID] = forumInfo.CopyTo(forum); //Save the Forum to the db.
        }
        else
        {
            repository.Add(ForumFactory.CreateFrom(forumInfo)); //Insert the Forum into the db.
        }
    }
}

我在尝试这个原型时遇到的大部分问题是,我在尝试学习 DDD 的过程中过于关注基础设施问题(即 DI、服务器架构等)。我对 DDD 的了解使我得出这样的结论:当试图弄清楚如何构建 DDD 解决方案时,请忘记架构和技术,直到您很好地掌握了 DDD 将为您做什么。我做这个注释是因为在我目前正在从事的实际 DDD 项目中,DI 似乎是一个不必要的复杂化。这就是 DDD 使您的代码变得如此简单的原因。

I am trying to achieve a proof of concept where I write a module (let's say Forum for the purpose of this discussion) that follows Domain Driven Design guidelines and will have a pluggable repository and the entire module will be pluggable either locally in a web server (local hosted dll) or through WCF services.

Following Domain Driven Design guidelines, I would have a set of business objects that would be written like this:

    public class Forum
    {
        readonly IRepository _forumRepository;
        public Forum(IRepository forumRepository)
        {
            _forumRepository = forumRepository;
        }

        public string Name { get; set; }

        public void Save()
        {
            _forumRepository.SaveForum(this);
        }
    }

Let's say the web application needs to create a new forum.

This is all well and good if the module is hosted locally via a dll file. The web application code would simply use Ninject (or whatever DI) to instantiate the business object and call the Save method.

However, what if the implementers wanted to introduce a service layer in between? Let's say they want to introduce a WCF Services layer in between the application and the database layer because they want the physical architecture to be Web Server -> App Server -> DB Server. Obviously, the Forum module dll would be hosted by both the web server and the app server. However, the module is no longer usable.

If the web server instantiates the object with the repository injection and passes it across the WCF Service layer, the _forumRepository field will get lost even if I allow for my business objects to be serialized.

The implementor should be able to make the application server choose the repository anyway. So, with this requirement, how would the implementor inject the repository on the application server side after receiving the already instantiated object from the web server? Is there a way to tell WCF Services to inject the repository when it instantiates the object during the deserialization process?

I read the article, How to use Dependency Injection (Ninject) with WCF Services, and reviewed the example solution, but this only demonstrates how to inject the repository directly into the service. It doesn't appear to solve the problem I am discussing here.

Here is an example of how I foresee the code to be if written correctly. As I stated before, the main problem here is that I don't know how I can inject the repository into the domain object on the services side.

Application Code (Web App or Windows App):

Forum newForum = new Forum();
//Set some properties on newForum.        
IKernel kernel = new StandardKernel();  //Instantiate ninject kernel.
//Yes, I know using the using statement means that IService needs to derive from IDisposable.  A local service would just have an empty implementation for IDisposable since there would be nothing to clean up and this would allow different service architectures to be plugged into the application.
using (IForumService forumService = kernel.Get<IForumService>())  //Get concrete implementation bound to IForumService via ninject.
{
    //Send forum to service layer for local OR WCF processing (on an app server)
    forumService.SaveForum(newForum);
}

Services Code (This is the problem):

public class WCFForumService : IForumService
{
    public void SaveForum(Forum forum)
    {
        //PROBLEM SCENARIO:  How do I now inject the repository I want to use since I already have an instance of forum?  
        //Can I make WCF inject the repository when it is deserializing the forum object before passing it into this method?
        forum.Save();
    }
}

I would prefer to not force implementers of my forum module into creating DTOs for every domain object if at all possible. This violates the DRY principle. It would be great if I could create my domain objects so they are generic and/or extensible enough to be used as the DTOs themselves with very little effort. This way, I can put business rule validation on the domain objects using Data Annotations and the implementer can use these objects in a Web Forms, MVC3, or Silverlight project without having to re-work all of the validation manually.

EDIT: THE ABOVE IS ENTIRELY THE WRONG APPROACH. EDITING TO DEMONSTRATE A MORE VALID APPROACH.

public class Forum
{
    public Forum(string name)
    {
        Name = name;
    }

    public string Name { get; set; }
}

public interface IForumRepository : IRepository<Forum>
{
    void Add(Forum forum);
    Forum this[object id] { get; set; }
}

//Client Code (Called from the WCF service hosting the domain)
public class WCFAppService
{
    public void SaveForum(ForumDTO forumInfo)
    {
        IKernel kernel = StandardKernel();
        IForumRepository repository = kernel.Get<IForumRepository>();
        Forum forum = repository[forumInfo.ID];
        if (forum != null)
        {
            repository[forumInfo.ID] = forumInfo.CopyTo(forum); //Save the Forum to the db.
        }
        else
        {
            repository.Add(ForumFactory.CreateFrom(forumInfo)); //Insert the Forum into the db.
        }
    }
}

Much of the problem that I had when attempting this prototype was that I was focusing too much on the infrastructure concerns (ie. DI, server architecture, etc) while trying to learn DDD in the process. What I have learned about DDD brings me to the conclusion that, when attempting to figure out how to structure a DDD solution, forget about architecture and technology until you have a good grip on what DDD will do for you. I make this note because in my actual DDD project that I am currently working on, DI seems like an unnecessary complication. That's how simple DDD can make your code.

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

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

发布评论

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

评论(2

瑾夏年华 2024-12-19 05:57:23

通过将存储库传递到 Web 服务器,您可以跳过层。没有什么可以阻止您调用 save 或以业务规则不允许的方式操作数据。此外,您不想关心 Web 服务器上的数据库详细信息。这是应用程序服务器的职责。

应用程序和数据库服务器之间应该有一个完全不同的接口,就像应用程序和网络服务器之间一样。应用程序服务器的界面应包含您要在 Web 服务器上执行的操作。

假设您有一个银行账户系统。并希望在两个帐户之间执行交易。在这种情况下,在您的解决方案中,您将两个涉及的帐户发送到网络服务器,它会根据转移的金额减少第一个帐户的余额,并增加第二个帐户的相同金额。然后它将帐户发送回应用程序服务器进行保存。

但这是错误的。正确的做法是在应用程序服务器上提供一个服务,提供一个交易方法,该方法接受涉及的帐号和要转账的金额,并在应用程序服务器上完成完整的操作。

在您的示例中,您应该提供一个方法 ChangeForumName(int forumId, string newName)。现在您可以在此处确保名称必须满足的规则。例如不为空。

By passing your repositories to the web server you are skipping layers. Nothing prevents you from calling save there or manipulate data in a way that is not allowed by your business rules. Furthermore, you don't want to care about database details on the web server. This is the responsibility of the app server.

There should be a completely different interface between app and db server as between app and web server. The interface of the app server should consist of the actions you want to perform on your web server.

Lets say you have a bank accout system. And want to perform a transaction between two accouts. In this case in your solution you would send the two involved accouts to the webserver and it decreases the balance of the first account by the transfered amount and increases the second one by the same amount. Then it sends the accounts back to the appserver for saving.

But this is wrong. What's correct is to provide a service on the app server that provides a transaction method that takes the involved account numbers and the amount to be transfered and the complete operation is done on the app server.

In your example you should provide a method ChangeForumName(int forumId, string newName). Here you can now ensure the rules that the name must fulfill. E.g. not empty.

能怎样 2024-12-19 05:57:23

如果您确实希望在通过线路发送域对象后将其包含在域对象中,那么请将您的服务作为对象的属性,并在“Get”方法中使用某种 CommonServiceLocator 模式来获取另一端的服务。请参阅此处了解更多信息 http://commonservicelocator.codeplex.com/ (即不是 WCF 注入服务,该对象调用 CommonServiceLocator 来获取其所需服务的实例)。

另一种方法可能是编写注入服务的自定义 wcf 行为。查看这篇文章了解更多详细信息 - http://msdn.microsoft.com/en -us/magazine/cc163302.aspx

但是,您应该更仔细地考虑您的体系结构以及您想要实现的目标。如果您想要 Web 服务器->应用程序服务器-->数据库服务器,那么您实际上不应该将对象发送到应用程序服务器,以便应用程序服务器可以注入服务,然后在您的对象上调用“保存”方法。更简洁的设计是拥有一个应用程序服务器(在本例中为 wcf),该服务器具有一个带有 save 方法的服务,该方法将“Forum”对象作为参数。然后,该服务将根据您引用的文章将存储库注入其中。

如您所知,使用 WCF 时,您实际上只是通过线路发送序列化数据,而不是实际对象。这些物体在另一侧被重建。许多体系结构使用数据传输对象进行 WCF 调用,然后使用这些 DTO 来合并其域模型(尽管您可以仅使用域对象来实现此目的,请记住您只是发送数据)。

本文对这些主题进行了很好的讨论 http://msdn.microsoft.com/ en-us/magazine/ee236638.aspx (尽管您的问题与 DTO 无关,但该原则也适用于域对象,并且可能会给您一些关于如何放置这些片段的思考 一起)。也值得看看其他人是如何做类似的事情的。有一些很棒的示例应用程序,例如 http://sankarsan.wordpress.com/2009/04/12/a-layered-aspnet-mvc-application-part-i/(您需要阅读整个系列,因为他没有谈论前 2 部分中的 wcf)

If you really really wanted to have the services in the domain object after sending it over the wire, then have your services as properties of your objects and in the "Get" method use some sort of CommonServiceLocator pattern to get the services at the other side. See here for more info http://commonservicelocator.codeplex.com/ (i.e. rather than WCF injecting the services, the object calls out to CommonServiceLocator to get instances of the services it requires).

Another approach might be to write a custom wcf behaviour that injected the services. Check out this article for more details - http://msdn.microsoft.com/en-us/magazine/cc163302.aspx

However, you should think more closely about your architecture and what you want to achieve. If you wanted to have Web Server->App Server-->Db Server then really you should not be sending your object to your app server so that the app server can inject services and then call a "save" method on your object. A cleaner design would be to have an app server (wcf in this case) that has a service with a save method that takes your "Forum" object as a parameter. That service would then have the repository injected into it as per the article you referenced.

As you know, when using WCF you are really only sending serialized data across the wire and not actual objects. The objects are reconstructed on the other side. Lots of architectures use Data Transfer Objects for their WCF calls and then hydrate their domain models with these DTOs (although you can just use your domain objects for this remember that you are just sending the data).

This article has a good discussion on these topics http://msdn.microsoft.com/en-us/magazine/ee236638.aspx (althoght your question wasn't about DTOs the principle applies to domain objects as well, and may give you some food for thought around how you are putting the pieces together). Its also worth looking at how others are doing similar things. There are some great sample apps around e.g. http://sankarsan.wordpress.com/2009/04/12/a-layered-aspnet-mvc-application-part-i/ (you need to read the whole series as he doesn't talk about wcf in the first 2 parts)

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