[.NET领域驱动设计实战系列]专题九:DDD案例:网上书店AOP和站点地图的实现

一、引言

  在前面一专题介绍到,要让缓存生效还需要实现对AOP(面向切面编程)的支持。所以本专题将介绍了网上书店案例中AOP的实现。关于AOP的概念,大家可以参考文章:http://www.cnblogs.com/jin-yuan/p/3811077.html。这里我简单介绍下AOP:AOP可以理解为对方法进行截获,这样就可以在方法调用前或调用后插入需要的逻辑。例如可以在方法调用前,加入缓存查找逻辑等。这里缓存查找逻辑就在方法调用前被执行。通过对AOP的支持,每个方法就可以分为3部分了,方法调用前逻辑->具体需要调用的方法->方法调用后的逻辑。也就是在方法调用的时候“切了一刀”。

二、网上书店AOP的实现

  你可以从零开始去实现AOP,但是目前已经存在很多AOP框架了,所以在本案例中将直接通过Unity的AOP框架(Unity.Interception)来实现网上书店对AOP的支持。通常AOP的实现放在基础设施层进行实现,因为可能其他所有层都需要加入对AOP的支持。本案例中将对两个方面的AOP进行实现,一个是方法调用前缓存的记录或查找,另一个是方法调用后异常信息的记录。在实现具体代码之前,我们需要在基础设施层通过Nuget来引入Unity.Interception包。添加成功之后,我们需要定义两个类分别去实现AOP框架中IInterceptionBehavior接口。由于本案例中需要对缓存和异常日志功能进行AOP实现,自然就需要定义CachingBehavior和ExceptionLoggingBehavior两个类去实现IInterceptionBehavior接口。首先让我们看看CachingBehavior类的实现,具体实现代码如下所示:

 // 缓存AOP的实现
    public class CachingBehavior : IInterceptionBehavior
    {
        private readonly ICacheProvider _cacheProvider;

        public CachingBehavior()
        {
            _cacheProvider = ServiceLocator.Instance.GetService<ICacheProvider>();
        }

        // 生成缓存值的键值
        private string GetValueKey(CacheAttribute cachingAttribute, IMethodInvocation input)
        {
            switch (cachingAttribute.Method)
            {
                // 如果是Remove,则不存在特定值键名,所有的以该方法名称相关的缓存都需要清除
                case CachingMethod.Remove:
                    return null;
                // 如果是Get或者Update,则需要产生一个针对特定参数值的键名
                case CachingMethod.Get:
                case CachingMethod.Update:
                    if (input.Arguments != null &&
                        input.Arguments.Count > 0)
                    {
                        var sb = new StringBuilder();
                        for (var i = 0; i < input.Arguments.Count; i++)
                        {
                            sb.Append(input.Arguments[i]);
                            if (i != input.Arguments.Count - 1)
                                sb.Append("_");
                        }

                        return sb.ToString();
                    }
                    else
                        return "NULL";
                default:
                    throw new InvalidOperationException("无效的缓存方式。");
            }
        }

        #region IInterceptionBehavior Members
        public IEnumerable<Type> GetRequiredInterfaces()
        {
            return Type.EmptyTypes;
        }

        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            // 获得被拦截的方法
            var method = input.MethodBase;
            var key = method.Name; // 获得拦截的方法名
            // 如果拦截的方法定义了Cache属性,说明需要对该方法的结果需要进行缓存
            if (!method.IsDefined(typeof (CacheAttribute), false))
                return getNext().Invoke(input, getNext);

            var cachingAttribute = (CacheAttribute)method.GetCustomAttributes(typeof (CacheAttribute), false)[0];
            var valueKey = GetValueKey(cachingAttribute, input);
            switch (cachingAttribute.Method)
            {
                case CachingMethod.Get:
                    try
                    {
                        // 如果缓存中存在该键值的缓存,则直接返回缓存中的结果退出
                        if (_cacheProvider.Exists(key, valueKey))
                        {
                            var value = _cacheProvider.Get(key, valueKey);
                            var arguments = new object[input.Arguments.Count];
                            input.Arguments.CopyTo(arguments, 0);
                            return new VirtualMethodReturn(input, value, arguments);
                        }
                        else // 否则先调用方法,再把返回结果进行缓存
                        {
                            var methodReturn = getNext().Invoke(input, getNext);
                            _cacheProvider.Add(key, valueKey, methodReturn.ReturnValue);
                            return methodReturn;
                        }
                    }
                    catch (Exception ex)
                    {
                        return new VirtualMethodReturn(input, ex);
                    }
                case CachingMethod.Update:
                    try
                    {
                        var methodReturn = getNext().Invoke(input, getNext);
                        if (_cacheProvider.Exists(key))
                        {
                            if (cachingAttribute.IsForce)
                            {
                                _cacheProvider.Remove(key);
                                _cacheProvider.Add(key, valueKey, methodReturn.ReturnValue);
                            }
                            else
                                _cacheProvider.Update(key, valueKey, methodReturn);
                        }
                        else
                            _cacheProvider.Add(key, valueKey, methodReturn.ReturnValue);

                        return methodReturn;
                    }
                    catch (Exception ex)
                    {
                        return new VirtualMethodReturn(input, ex);
                    }
                case CachingMethod.Remove:
                    try
                    {
                        var removeKeys = cachingAttribute.CorrespondingMethodNames;
                        foreach (var removeKey in removeKeys)
                        {
                            if (_cacheProvider.Exists(removeKey))
                                _cacheProvider.Remove(removeKey);
                        }
                         // 执行具体截获的方法
                        var methodReturn = getNext().Invoke(input, getNext);
                        return methodReturn;
                    }
                    catch (Exception ex)
                    {
                        return new VirtualMethodReturn(input, ex);
                    }
                default: break;
            }

            return getNext().Invoke(input, getNext);
        }

        public bool WillExecute
        {
            get { return true; }
        }
        #endregion
    }

  从上面代码可以看出,通过Unity.Interception框架来实现AOP变得非常简单了,我们只需要实现IInterceptionBehavior接口中的Invoke方法和WillExecute属性即可。并且从上面代码可以看出,AOP的支持最核心代码实现在于Invoke方法的实现。既然我们需要在方法调用前查找缓存,如果缓存不存在再调用方法从数据库中进行查找,如果存在则直接从缓存中进行读取数据即可。自然需要在 getNext().Invoke(input, getNext)代码执行前进缓存进行查找,然而上面CachingBehavior类正式这样实现的。

  介绍完缓存功能AOP的实现之后,下面具体看看异常日志的AOP实现。具体实现代码如下所示:

// 用于异常日志记录的拦截行为
    public class ExceptionLoggingBehavior :IInterceptionBehavior
    {
        /// <summary>
        /// 需要拦截的对象类型的接口
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Type> GetRequiredInterfaces()
        {
            return Type.EmptyTypes;
        }

        /// <summary>
        /// 通过该方法来拦截调用并执行所需要的拦截行为
        /// </summary>
        /// <param name="input">调用拦截目标时的输入信息</param>
        /// <param name="getNext">通过行为链来获取下一个拦截行为的委托</param>
        /// <returns>从拦截目标获得的返回信息</returns>
        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            // 执行目标方法
            var methodReturn = getNext().Invoke(input, getNext);
            // 方法执行后的处理
            if (methodReturn.Exception != null)
            {
                Utils.Log(methodReturn.Exception);
            }

            return methodReturn;
        }

        // 表示当拦截行为被调用时,是否需要执行某些操作
        public bool WillExecute
        {
            get { return true; }
        }
    }

  异常日志功能的AOP实现与缓存功能的AOP实现类似,只是一个需要在方法执行前注入,而一个是在方法执行后进行注入罢了,其实现原理都是在截获的方法前后进行。方法截获功能AOP框架已经帮我们实现了。

  到此,我们网上书店AOP的实现就已经完成了,但要正式生效还需要通过配置文件把AOP的实现注入到需要截获的方法当中去,这样执行这些方法才会执行注入的行为。对应的配置文件如下标红部分所示:

 <!--Unity的配置信息-->
  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
    <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration" />
    <container>
      <extension type="Interception" />

       <!--Cache Provider-->
      <register type="OnlineStore.Infrastructure.Caching.ICacheProvider, OnlineStore.Infrastructure" mapTo="OnlineStore.Infrastructure.Caching.EntLibCacheProvider, OnlineStore.Infrastructure" />

      <!--仓储接口的注册-->
      <register type="OnlineStore.Domain.Repositories.IRepositoryContext, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.EntityFrameworkRepositoryContext, OnlineStore.Repositories">
          <lifetime type="singleton" />
      </register>
      <register type="OnlineStore.Domain.Repositories.IProductRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.ProductRepository, OnlineStore.Repositories" />
      <register type="OnlineStore.Domain.Repositories.ICategoryRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.CategoryRepository, OnlineStore.Repositories" />
      <register type="OnlineStore.Domain.Repositories.IProductCategorizationRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.ProductCategorizationRepository, OnlineStore.Repositories" />
      <register type="OnlineStore.Domain.Repositories.IUserRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.UserRepository, OnlineStore.Repositories" />
      <register type="OnlineStore.Domain.Repositories.IShoppingCartRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.ShoppingCartRepository, OnlineStore.Repositories" />
      <register type="OnlineStore.Domain.Repositories.IShoppingCartItemRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.ShoppingCartItemRepository, OnlineStore.Repositories" />
      <register type="OnlineStore.Domain.Repositories.IOrderRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.OrderRepository, OnlineStore.Repositories" />
      <register type="OnlineStore.Domain.Repositories.IUserRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.UserRepository, OnlineStore.Repositories" />
      <register type="OnlineStore.Domain.Repositories.IUserRoleRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.UserRoleRepository, OnlineStore.Repositories" />
      <register type="OnlineStore.Domain.Repositories.IRoleRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.RoleRepository, OnlineStore.Repositories" />

       <!--Domain Services-->
      <register type="OnlineStore.Domain.Services.IDomainService, OnlineStore.Domain" mapTo="OnlineStore.Domain.Services.DomainService, OnlineStore.Domain" />
      <!--应用服务的注册-->
      <register type="OnlineStore.ServiceContracts.IProductService, OnlineStore.ServiceContracts" mapTo="OnlineStore.Application.ServiceImplementations.ProductServiceImp, OnlineStore.Application">
        <!--注入AOP功能的实现-->
        <interceptor type="InterfaceInterceptor" />
        <interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.CachingBehavior, OnlineStore.Infrastructure" />
        <interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.ExceptionLoggingBehavior, OnlineStore.Infrastructure" />
      </register>

      <register type="OnlineStore.ServiceContracts.IOrderService, OnlineStore.ServiceContracts" mapTo="OnlineStore.Application.ServiceImplementations.OrderServiceImp, OnlineStore.Application">
        <!--注入AOP功能的实现-->
        <interceptor type="InterfaceInterceptor" />
        <interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.CachingBehavior, OnlineStore.Infrastructure" />
        <interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.ExceptionLoggingBehavior, OnlineStore.Infrastructure" />
      </register>
      <register type="OnlineStore.ServiceContracts.IUserService, OnlineStore.ServiceContracts" mapTo="OnlineStore.Application.ServiceImplementations.UserServiceImp, OnlineStore.Application">
        <!--注入AOP功能的实现-->
        <interceptor type="InterfaceInterceptor" />
        <interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.CachingBehavior, OnlineStore.Infrastructure" />
        <interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.ExceptionLoggingBehavior, OnlineStore.Infrastructure" />
      </register>

      <!--Domain Event Handlers-->
      <register type="OnlineStore.Domain.Events.IDomainEventHandler`1[[OnlineStore.Domain.Events.OrderDispatchedEvent, OnlineStore.Domain]], OnlineStore.Domain" mapTo="OnlineStore.Domain.Events.EventHandlers.OrderDispatchedEventHandler, OnlineStore.Domain" name="OrderDispatchedEventHandler" />
      <register type="OnlineStore.Domain.Events.IDomainEventHandler`1[[OnlineStore.Domain.Events.OrderConfirmedEvent, OnlineStore.Domain]], OnlineStore.Domain" mapTo="OnlineStore.Domain.Events.EventHandlers.OrderConfirmedEventHandler, OnlineStore.Domain" name="OrderConfirmedEventHandler" />

      <!--Event Handlers-->
      <register name="orderSendEmailHandler" type="OnlineStore.Events.IEventHandler`1[[OnlineStore.Domain.Events.OrderDispatchedEvent, OnlineStore.Domain]], OnlineStore.Events" mapTo="OnlineStore.Events.Handlers.SendEmailHandler, OnlineStore.Events.Handlers" />

      <!--Event Aggregator-->
      <register type="OnlineStore.Events.IEventAggregator, OnlineStore.Events" mapTo="OnlineStore.Events.EventAggregator, OnlineStore.Events">
        <constructor>
          <param name="handlers">
            <array>
              <dependency name="orderSendEmailHandler" type="OnlineStore.Events.IEventHandler`1[[OnlineStore.Domain.Events.OrderDispatchedEvent, OnlineStore.Domain]], OnlineStore.Events" />
            </array>
          </param>
        </constructor>
      </register>

      <!--Event Bus-->
      <!--<register type="OnlineStore.Events.Bus.IEventBus, OnlineStore.Events" mapTo="OnlineStore.Events.Bus.EventBus, OnlineStore.Events">
        <lifetime type="singleton" />
      </register>-->

      <!--注入MsmqEventBus-->
      <register type="OnlineStore.Events.Bus.IEventBus, OnlineStore.Events"
                mapTo="OnlineStore.Events.Bus.MsmqEventBus, OnlineStore.Events">
        <lifetime type="singleton" />
        <constructor>
          <param name="path" value=".\Private$\OnlineStoreQueue" />
        </constructor>
      </register>
    </container>
  </unity>
  <!--END: Unity-->

  到此,网上书店案例中AOP的实现就完成了。通过上面的配置可以看出,客户端在调用应用服务方法前后会调用我们注入的行为,即缓存行为和异常日志行为。通过对AOP功能的支持,就不需要为每个需要进行缓存或需要异常日志行为的方法来重复写这些相同的逻辑了。从而避免了重复代码的重复实现,提高了代码的重用性和降低了模块之间的依赖性。

三、网上书店案例中站点地图的实现

  在大部分网站中都实现了站点地图的功能,在Asp.net中,我们可以通过SiteMap模块来实现站点地图的功能,在Asp.net MVC也可以通过MvcSiteMapProvider第三方开源框架来实现站点地图。所以针对网上书店案例,站点地图的支持也是必不可少的。下面让我们具体看看站点地图在本案例中是如何去实现的呢?

  在看实现代码之前,让我们先来理清下实现思路。

  本案例中站点地图的实现,并没有借助MvcSiteMapProvider第三方框架来实现。其实现原理首先获得用户的路由请求,然后根据用户请求根据站点地图的配置获得对应的配置节点,接着根据站点地图的节点信息生成类似">首页>"这样带标签的字符串;如果获得的节点是配置文件中某个父节点的子节点,此时会通过递归的方式找到其父节点,然后递归地生成对应带标签的字符串,从而完成站点地图的功能。分析完实现思路之后,下面让我们再对照下具体的实现代码来加深理解。具体的实现代码如下所示:

public class MvcSiteMap
    {
        private static readonly MvcSiteMap _instance = new MvcSiteMap();

        private static readonly XDocument Doc = XDocument.Load(HttpContext.Current.Server.MapPath(@"~/SiteMap.xml"));

        private UrlHelper _url = null;

        private string _currentUrl;

        public static MvcSiteMap Instance
        {
            get { return _instance;}
        }

        private MvcSiteMap()
        {
        }

        public MvcHtmlString Navigator()
        {
            // 获得当前请求的路由信息
            _url = new UrlHelper(HttpContext.Current.Request.RequestContext);
            var routeUrl = _url.RouteUrl(HttpContext.Current.Request.RequestContext.RouteData.Values);
            if (routeUrl != null)
                _currentUrl = routeUrl.ToLower(); 

            // 从配置的站点Xml文件中找到当前请求的Url相同的节点
            var c = FindNode(Doc.Root);
            var temp = GetPath(c);

            return MvcHtmlString.Create(BuildPathString(temp));
        }

        // 从SitMap配置文件中找到当前请求匹配的节点
        private XElement FindNode(XElement node)
        {
            // 如果xml节点对应的url是否与当前请求的节点相同,如果相同则直接返回xml对应的节点
            // 如果不同开始递归子节点
            return IsUrlEqual(node) == true ? node : RecursiveNode(node);
        }

        // 判断xml节点对应的url是否与当前请求的url一样
        private bool IsUrlEqual(XElement c)
        {
            var a = GetNodeUrl(c).ToLower();
            return a == _currentUrl;
        }

        // 递归Xml节点
        private XElement RecursiveNode(XElement node)
        {
            foreach (var c in node.Elements())
            {
                if (IsUrlEqual(c) == true)
                {
                    return c;
                }
                else
                {
                    var x = RecursiveNode(c);
                    if (x != null)
                    {
                        return x;
                    }
                }
            }

            return null;
        }

        // 获得xml节点对应的请求url
        private string GetNodeUrl(XElement c)
        {
            return _url.Action(c.Attribute("action").Value, c.Attribute("controller").Value,
                new {area = c.Attribute("area").Value});
        }

        // 根据对应请求url对应的Xml节点获得其在Xml中的路径,即获得其父节点有什么
        // SiteMap.xml 中节点的父子节点一定要配置对
        private Stack<XElement> GetPath(XElement c)
        {
            var temp = new Stack<XElement>();
            while (c != null)
            {
                temp.Push(c);
                c = c.Parent;
            }
            return temp;
        }

        // 根据节点的路径来拼接带标签的字符串
        private string BuildPathString(Stack<XElement> m)
        {
            var sb = new StringBuilder();
            var tc = new TagBuilder("span");
            tc.SetInnerText(">");
            var sp = tc.ToString();
            var count = m.Count;
            for (var x = 1; x <= count; x++)
            {
                var c = m.Pop();
                TagBuilder tb;
                if (x == count)
                {
                    tb = new TagBuilder("span");
                }
                else
                {
                    tb = new TagBuilder("a");
                    tb.MergeAttribute("href", GetNodeUrl(c));
                }

                tb.SetInnerText(c.Attribute("title").Value);
                sb.Append(tb);
                sb.Append(sp);
            }

            return sb.ToString();
        }
    }

  对应的站点地图配置信息如下所示:

<?xml version="1.0" encoding="utf-8" ?>
<node title="首页" area="" action="Index" controller="Home">
  <node title="我的" area="UserCenter" action="Manage" controller="Account">
    <node title="订单" area="" action="Orders" controller="Home" />
    <node title="账户" area="" action="Manage" controller="Account" />
    <node title="购物车" area="" action="ShoppingCart" controller="Home" />
  </node>
  <node title="关于" area="AboutCenter" action="About" controller="Home" >
    <node title="Online Store 项目" area="" action="About" controller="Home" />
    <node title="联系方式" area="" action="Contact" controller="Home" />
  </node>
</node>

  实现完成之后,下面让我们具体看看本案例中站点地图的实现效果看看,具体运行效果如下图所示:

四、小结

  到这里,本专题的内容就结束了。本专题主要借助Unity.Interception框架在网上书店中引入了AOP功能,并且最后简单介绍了站点地图的实现。在下一专题将对CQRS模式做一个全面的介绍。

  本案例所有源码:https://github.com/lizhi5753186/OnlineStore_Second/

时间: 2024-12-22 01:49:42

[.NET领域驱动设计实战系列]专题九:DDD案例:网上书店AOP和站点地图的实现的相关文章

[.NET领域驱动设计实战系列]专题十一:.NET 领域驱动设计实战系列总结

一.引用 其实在去年本人已经看过很多关于领域驱动设计的书籍了,包括Microsoft .NET企业级应用框架设计.领域驱动设计C# 2008实现.领域驱动设计:软件核心复杂性应对之道.实现领域驱动设计和Asp.net 设计模式等书,但是去年的学习仅仅限制于看书,当时看下来感觉,领域驱动设计并没有那么难,并且感觉有些领域驱动设计的内容并没有好的,反而觉得有点华而不实的感觉,所以去年也就放弃了领域驱动设计系列的分享了,但是到今年,在博客园看到还是有很多人写领域驱动的文章,以及介绍了领域驱动设计相关的

[.NET领域驱动设计实战系列]专题一:前期准备之EF CodeFirst

一.前言 从去年已经接触领域驱动设计(Domain-Driven Design)了,当时就想自己搭建一个DDD框架,所以当时看了很多DDD方面的书,例如领域驱动模式与实战,领域驱动设计:软件核心复杂性应对之道和领域驱动设计C# 2008实现等书,由于当时只是看看而已,并没有在自己代码中进行实现,只是初步了解一些DDD分层的思想和一些基本概念,例如实体,聚合根.仓储等概念,今年有机会可以去试试面试一个架构岗位的时候,深受打击,当面试官问起是否在项目中使用过DDD思想来架构项目时,我说没有,只是了解

[.NET领域驱动设计实战系列]专题二:结合领域驱动设计的面向服务架构来搭建网上书店

一.前言 在前面专题一中,我已经介绍了我写这系列文章的初衷了.由于dax.net中的DDD框架和Byteart Retail案例并没有对其形成过程做一步步分析,而是把整个DDD的实现案例展现给我们,这对于一些刚刚接触领域驱动设计的朋友可能会非常迷茫,从而觉得领域驱动设计很难,很复杂,因为学习中要消化一个整个案例的知识,这样未免很多人消化不了就打退堂鼓,就不继续研究下去了,所以这样也不利于DDD的推广.然而本系列可以说是刚接触领域驱动设计朋友的福音,本系列将结合领域驱动设计的思想来一步步构建一个网

[.NET领域驱动设计实战系列]专题十:DDD扩展内容:全面剖析CQRS模式实现

一.引言 前面介绍的所有专题都是基于经典的领域驱动实现的,然而,领域驱动除了经典的实现外,还可以基于CQRS模式来进行实现.本专题将全面剖析如何基于CQRS模式(Command Query Responsibility Segregation,命令查询职责分离)来实现领域驱动设计. 二.CQRS是什么? 在介绍具体的实现之前,对于之前不了解CQRS的朋友来说,首先第一个问题应该是:什么是CQRS啊?你倒是详细介绍完CQRS后再介绍具体实现啊?既然大家会有这样的问题,所以本专题首先全面介绍下什么是

[.NET领域驱动设计实战系列]专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能

一.引言 在当前的电子商务平台中,用户下完订单之后,然后店家会在后台看到客户下的订单,然后店家可以对客户的订单进行发货操作.此时客户会在自己的订单状态看到店家已经发货.从上面的业务逻辑可以看出,当用户下完订单之后,店家或管理员可以对客户订单进行跟踪和操作.上一专题我们已经实现创建订单的功能,则接下来自然就是后台管理功能的实现了.所以在这一专题中将详细介绍如何在网上书店案例中实现后台管理功能. 二.后台管理中的权限管理的实现 后台管理中,首先需要实现的自然就是权限管理了,因为要进行商品管理等操作的

[.NET领域驱动设计实战系列]专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现

一.引言 在上一专题中,商家发货和用户确认收货功能引入了消息队列来实现的,引入消息队列的好处可以保证消息的顺序处理,并且具有良好的可扩展性.但是上一专题消息队列是基于内存中队列对象来实现,这样实现有一个弊端,就是一旦服务重启或出现故障时,此时消息队列中的消息会丢失,并且也记录不了日志.所以就会出现,商家发货成功后,用户并没有收到邮件通知,并且也没有日志让我们发现是否发送了邮件通知.为了解决这个问题,就需要引入一种可恢复的消息队列.目前有很多开源的消息队列都支持可恢复的,例如TibcoEms.ne

[.NET领域驱动设计实战系列]专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现

一.前言 在前面2篇博文中,我分别介绍了规约模式和工作单元模式,有了前面2篇博文的铺垫之后,下面就具体看看如何把这两种模式引入到之前的网上书店案例里. 二.规约模式的引入 在第三专题我们已经详细介绍了什么是规约模式,没看过的朋友首先去了解下.下面让我们一起看看如何在网上书店案例中引入规约模式.在网上书店案例中规约模式的实现兼容了2种模式的实现,兼容了传统和轻量的实现,包括传统模式的实现,主要是为了实现一些共有规约的重用,不然的话可能就要重复写这些表达式.下面让我们具体看看在该项目中的实现. 首先

[.NET领域驱动设计实战系列]专题三:前期准备之规约模式(Specification Pattern)

一.前言 在专题二中已经应用DDD和SOA的思想简单构建了一个网上书店的网站,接下来的专题中将会对该网站补充更多的DDD的内容.本专题作为一个准备专题,因为在后面一个专题中将会网上书店中的仓储实现引入规约模式.本专题将详细介绍了规约模式. 二.什么是规约模式 讲到规约模式,自然想到的是什么是规约模式呢?从名字上看,规约模式就是一个约束条件,我们在使用仓储进行查询的时候,这时候就会牵涉到很多查询条件,例如名字包含C#的书名等条件.这样就自然需要引入规约模式了.规约模式的作用可以自由组装业务逻辑元素

NET 领域驱动设计实战系列总结

NET 领域驱动设计实战系列总结 一.引用 其实在去年本人已经看过很多关于领域驱动设计的书籍了,包括Microsoft .NET企业级应用框架设计.领域驱动设计C# 2008实现.领域驱动设计:软件核心复杂性应对之道.实现领域驱动设计和Asp.net 设计模式等书,但是去年的学习仅仅限制于看书,当时看下来感觉,领域驱动设计并没有那么难,并且感觉有些领域驱动设计的内容并没有好的,反而觉得有点华而不实的感觉,所以去年也就放弃了领域驱动设计系列的分享了,但是到今年,在博客园看到还是有很多人写领域驱动的