领域驱动设计实现之路

2004年,Eric Evans出版《领域驱动设计——软件核心复杂性应对之道》(简称《领域驱动设计》),10年之后,我们有了《实现领域驱动设计》。DDD将一个软件系统的核心业务功能集中在一个核心域里面,其中包含了实体、值对象、领域服务、资源库和聚合等概念。在此基础上,DDD提出了一套完整的支撑这样的核心领域的基础设施。此时,DDD已经不再是“面向对象进阶”那么简单了,而是演变成了一个系统工程。 领域,即是一个组织的业务开展方式,业务价值便体现在其中。长久以来,我们程序员都是很好的技术型思考者,我们总是擅长从技术的角度来解决项目问题。但是,一个软件系统是否真正可用是通过它所提供的业务价值体现出来的。因此,与其每天钻在那些永远也学不完的技术中,何不将我们的关注点向软件系统所提供的业务价值方向思考思考,这也正是DDD所试图解决的问题。在DDD中,代码就是设计本身,你不再需要那些繁文缛节的并且永远也无法得到实时更新的设计文档。编码者与领域专家再也不需要翻译才能理解对方所表达的意思。DDD有战略设计和战术设计之分。战略设计主要从高层“俯视”我们的软件系统,帮助我们精准地划分领域以及处理各个领域之间的关系;而战术设计则从技术实现的层面教会我们如何具体地实施DDD。 DDD之战略设计 DDD的战略设计主要包括领域/子域、通用语言、限界上下文和架构风格等概念。 1、领域和子域(Domain/Subdomain) 领域驱动设计关注点应该放在如何设计领域模型上,以及对领域模型的划分。 领域的概念:一个电商网站的领域包含了产品名录、订单、发票、库存和物流的概念。领域的划分是将一个大的领域划分成若干个子域。通常会将一个大型的软件系统拆分成若干个子系统。这种划分有可能是基于架构方面的考虑,也有可能是基于基础设施的。DDD对系统的划分是基于领域的,即基于业务。第一个问题,哪些概念应该建模在哪些子系统里面?第二个问题是,各个子系统之间的应该如何集成?如何解决?答案是:限界上下文和上下文映射图。 2、限界上下文(Bounded Context) 在一个领域/子域中,我们会创建一个概念上的领域边界,在这个边界中,任何领域对象都只表示特定于该边界内部的确切含义。这样边界便称为限界上下文。限界上下文和领域具有一对一的关系。将一个限界上下文中的所有概念,包括名词、动词和形容词全部集中在一起,我们便为该限界上下文创建了一套通用语言。通用语言是一个团队所有成员交流时所使用的语言,业务分析人员、编码人员和测试人员都应该直接通过通用语言进行交流。 上文中的各个子域之间的集成问题,是限界上下文之间的集成问题。防腐层负责与外部服务提供方打交道,还负责将外部概念翻译成自己的核心领域能够理解的概念。当然,防腐层只是限界上下文之间众多集成方式的一种,另外还有共享内核、开放主机服务等,具体细节请参考 《实现领域驱动设计》原书。限界上下文之间的集成关系也可以理解为是领域概念在不同上下文之间的映射关系,因此,限界上下文之间的集成也称为上下文映射图。 3、架构风格(Architecture) DDD并不要求采用特定的架构风格,因为它是对架构中立的。可以采用传统的三层式架构,也可以采用REST架构和事件驱动架构等。但是在《实现领域驱动设计》中,作者比较推崇事件驱动架构和六边形(Hexagonal)架构。在六边形架构中,已经不存在分层的概念,所有组件都是平等的。这主要得益于软件抽象的好处,即各个组件的之间的交互完全通过接口完成,而不是具体的实现细节。Robert C. Martin:抽象不应该依赖于细节,细节应该依赖于抽象。 采用六边形架构的系统中存在着很多端口和适配器的组合。端口表示的是一个软件系统的输入和输出,而适配器则是对每一个端口的访问方式。 DDD之战术设计 战略设计为我们提供一种高层视野来审视我们的软件系统,而战术设计则将战略设计进行具体化和细节化,它主要关注的是技术层面的实施,也是对我们程序员来得最实在的地方。 1、领域对象 领域对象能够准确地表达出业务意图,但多数时候却是充满getter和setter的领域对象,此时的领域对象已经不是领域对象了,而是Martin Fowler所称之为的贫血对象。.NET里面很好地支持非贫血对象。 2、实体vs值对象(Entity vs Value Object) 软件系统中实体表示那些具有生命周期并且会在其生命周期中发生改变的东西;而值对象则表示起描述性作用的并且可以相互替换的概念。同一个概念,在一个软件系统中被建模成了实体,但是在另一个系统中则有可能是值对象。 3、聚合(Aggregate) 聚合是DDD中最难理解的概念 ,聚合中所包含的对象之间具有密不可分的联系,他们是内聚在一起的。比如一辆汽车(Car)包含了引擎(Engine)、车轮 (Wheel)和油箱(Tank)等组件,缺一不可。一个聚合中可以包含多个实体和值对象,因此聚合也被称为根实体。聚合是持久化的基本单位,它和资源库具有一一对应的关系。 4、领域服务(Domain Service) 领域概念放在实体上不合适,放在值对象上也不合适,领域服务本来就是来处理这种场景的。比如对密码进行加密,可以创建一个 PasswordEncryptService来专门负责此事。 5、资源库(Repository) 资源库用于保存和获取聚合对象,资源库与DAO相似。但资源库和DAO存在显著区别。DAO只是对数据库的一层很薄的封装,而资源库则更加具有领域特征。另外,所有的实体都可以有相应的DAO,但并不是所有的实体都有资源库,只有聚合才有相应的资源库。资源库分为两种,一种是基于集合的,一种是基于持久化的。顾名思义,基于集合的资源库具有编程语言中集合的特征。 6、领域事件(Domain Event) 《领域驱动设计》中并没有提到领域事件,领域事件是最近几年才加入DDD生态系统的。微服务(Micro Service)的架构中,整个系统被分成了很多个轻量的程序模块,他们之间的数据一致性并不容易通过事务一致性完成,领域事件便可以用于处理上述问题,此时最终一致性取代了事务一致性,通过领域事件的方式达到各个组件之间的数据一致性。

May 20, 2019

.NET Core 3.0-preview3 发布

.NET Core 3.0 Preview 3已经发布,框架和ASP.NET Core有许多有趣的更新。这是最重要的更新列表。 下载地址 :https://aka.ms/netcore3download 。 .NET Core 3.0的更新: C#中对索引和范围的更多支持 支持.NET Standard 2.1。以.NET Standard项目文件为目标,并将netstandard2.1指定为目标框架。完整的.NET Framework不支持.NET Standard 2.1。 F#4.6和dotnet fsi命令。可以使用F#4.6和dotnet fsi命令的预览。FSI代表F#互动。 AssemblyDependencyResolver和resolver事件。从给定路径加载依赖程序集(之前不可能),解析程序事件可帮助我们更好地处理动态加载的本机依赖项。 Windows Forms应用程序的高DPI。最后,微软将Windows Forms应用程序推向了当今时代。96DPI不再适用,并且可以构建高DP Windows窗体应用程序。 ​ ASP.NET Core 3.0的更新: Razor组件的改进。现在2个项目合并成单个项目模板,Razor组件支持端点路由和预渲染,Razor组件可以托管在Razor类库中。还改进了事件处理和表单和验证支持。 运行时编译。它在ASP.NET Core 3.0模板中被禁用,但现在可以通过向项目添加特殊的NuGet包来打开它。 Worker Service 模板。需要编写Windows服务还是Linux守护进程?现在我们有了Worker Service 模板。 gRPC模板。与谷歌一起构建的gRPC是一种流行的远程过程调用(RPC)框架。此版本的ASP.NET Core在ASP.NET Core上引入了第一等的gRPC支持。 Angular模板使用Angular 7. Angular SPA模板现在使用Angular 7,在第一次稳定释放之前,它将被Angular 8替换。 SPA-s的身份验证。Microsoft通过此预览为单页应用程序添加了现成的身份验证支持。 SignalR与端点路由集成。小变化 - 现在使用端点路由定义SingalR路由。 SignalR Java客户端支持长轮询。即使在不支持或不允许WebSocket的环境中,SignalR Java客户端现在也可以使用。

March 6, 2019

终于决定了,将业余的大部分时间投入给C#开源世界

昨天.NET已经宣布正式开源,而我的信念更加坚定了:将业余的大部分时间投入给C#开源世界。 之前一直诟病于C#和.NET只属于微软的产品,现在完全不一样了,我自己的下一步计划: 一、学习计划 1、重新把前几年的项目进行一次全面梳理,把核心和精华的部分留下; 2、重新全面系统完整地把最新的.NET框架学习一边,并且结合大的开源项目研究,目标是基于国外开源系统改造出非常好用的系统; 3、将手头上关于.NET和C#的图书做一遍梳理,包括电子版的; 4、持续跟进最新的关于Mono和.NET开源方面的资讯; 二、工作计划 1、将目前公司的项目做好梳理、未未来的跨平台打好基础; 2、专心研究nopcommerce(电商)项目,并且基于它搭建一个云、端、移动app、网店一体化的电商项目(时间可能会很长),这个项目会为未来二次开源做准备; 3、专心研究CSLA .NET,这个是.NET的一个典范,而且包含了所有平台的样例,这是跨平台的先驱; 4、专心研究Orchard(cms)项目,并且基于它搭建一个云、端、移动app的公司企业网站系统,这个项目也会为未来开源做准备。 将慢慢地把cms和电商项目融入公司,打造一支技术能力强的团队。

November 14, 2014

微软正式MIT协议将开源.NET框

今天是C#程序员狂欢的日子,这真的是一门非常非常优雅的很棒的编程语言。 “Scott Guthrie宣布微软正式将开源.NET框架,使用MIT协议开源,让它在Linux和OS X系统上也能够运行。开发商们将能够在全球三个最大的操作系统上使用.NET框架了。” 这将是计算机软件行业载入史册的一天,.NET平台终于兑现它对世界的承诺,全面支持所有平台,我坚信这是安德鲁和mono老大的英明推动下、当然也是在时代的发展驱动下的结果,太棒了!

November 13, 2014

.NET Framework发展简历

.NET Framework1.0版本是在2002年正式发布的,当时获得了世界软件界的极大瞩目,影响深远,比尔盖茨说这是为未来10年的战略做准备,现在看来,微软的这个产品获得了巨大成功。 2005年,微软发布了.NET Framework2.0,这个版本是迄今为止最重要的一个版本,2.0为C#引入了范型和公共语言运行时。.NET 3.0版本是基于2.0的运行时的,引入了WPF和XAML语言,还有一项伟大的技术WCF。.NET3.5则引入了C#3.0,并且支持LINQ语言(一种全新的方式来检索数据)和Lambda表达式。.NET4.0是继2.0之后的一个重要版本,引入了动态语言和并行编程。 .NET Framework4.5是基于4.0版本的更新,主要有下面这些更新: 1、异步变成方式,在C#5.0中,异步编程变得更加简单; 2、引入了Windows Store Apps; 3、增强了数据访问方式,主要是Entity Framework5.0的发布; 4、增强了WPF和江MVC升级到了4.0;

August 8, 2013

[转]C#中的委托,匿名方法和Lambda表达式

一、简介 在.NET中,委托,匿名方法和Lambda表达式很容易发生混淆。我想下面的代码能证实这点。下面哪一个First会被编译?哪一个会返回我们需要的结果?即Customer.ID=5.答案是6个First不仅被编译,并都获得正确答案,且他们的结果一样。如果你对此感到困惑,那么请继续看这篇文章。 class Customer { public int ID { get``; set``; } public static bool Test(Customer x) { return x.ID == 5; } } ... List<Customer> custs = new List<Customer>(); custs.Add(``new Customer() { ID = 1 }); custs.Add(``new Customer() { ID = 5 }); custs.First(``new Func<Customer, bool``>(``delegate``(Customer x) { return x.ID == 5; })); custs.First(``new Func<Customer, bool``>((Customer x) => x.ID == 5)); custs.First(``delegate``(Customer x) { return x.ID == 5; }); custs.First((Customer x) => x.ID == 5); custs.First(x => x.ID == 5); custs.First(Customer.Test); 二、什么是委托? 现在你定义一个处理用户订单的购物车ShoppingCart类。管理层决定根据数量,价格等给客人折扣。做为其中的一部分,他们已经实现了处理订单时你要考虑一方面。不用考虑过多,你简单声明一个变量来保存有“吸引力的折扣”(magicDisCount),然后实现逻辑。 ...

August 8, 2013

[转]Emgu cv中的SIFT算法实现

SIFT算法大家都比较熟悉,网上的版本很多,刚开始接触时我主要研究的是C++,因为相对于C#,基于OPEN CV C++的SIFT算法资料更多,但是由于想要实现较为理想的界面效果,最终还是放弃了使用C++转而使用C#。 C#中SIFT算法主要分为三种: 1)脱离Emgu cv平台,完全手动实现所有SIFT算法函数,这样的程序虽然实现有些困难,但是完全可借助已有的C++程序做更改,而且这样做最大的好处就是对SIFT算法的原理有更深的理解。 2)实现时使用少量Emgu cv函数(例如影像的读取,灰度值获得等),但是大部分工作还是依赖于.net平台自行完成。 3)基本上程序完全借助于Emgu cv提供的接口,核心函数完全由Emgu cv提供。 前两类程序资源较多,大家也很容易下载,第三类程序资源相对较少,因此我今天简单为大家介绍第三类算法的实现方法,首先回顾一下SIFT算法计算步骤: 1. 尺度空间极值检测:搜索所有尺度上的图像位置。通过高斯微分函数来识别潜在的对于尺度和旋转不变的兴趣点。 2. 关键点定位:在每个候选的位置上,通过一个拟合精细的模型来确定位置和尺度。关键点的选择依据于它们的稳定程度。 3. 方向确定:基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向。所有后面的对图像数据的操作都相对于关键点的方向、尺度和位置进行变换,从而提供对于这些变换的不变性。 4. 关键点描述:在每个关键点周围的邻域内,在选定的尺度上测量图像局部的梯度。这些梯度被变换成一种表示,这种表示允许比较大的局部形状的变形和光照变化。 使用Emgu cv实现上述步骤非常简单,程序如下: //确定匹配图像 Bitmap bt1 = new Bitmap(@“C:\Users\GAOXIANG\Desktop\111.jpg”); Bitmap bt2 = new Bitmap(@“C:\Users\GAOXIANG\Desktop\222.jpg”); //将图像转为Emgu cv的处理格式 Image<Gray, byte> img1 = new Image<Gray, byte>(bt1); Image<Gray, byte> img2 = new Image<Gray, byte>(bt2); //使用Emgu cv探测图片特征点 SIFTDetector sift = new SIFTDetector(); var f1 = sift.DetectFeatures(img1, null); var f2 = sift.DetectFeatures(img2, null); 到此已经获得了两张相片的特征点f1,f2,接下来就是将相互匹配的特征点相连: for (int i = 0; i < f1.Length; i++) { double[] dist = new double[f2.Length]; int n = 0; int index = 0; for (int j = 0; j < f2.Length; j++) { ...

November 29, 2012

HttpContext.Current用法,.ashx文件中使用Session,在非Web项目中使用HttpContext.Current

在类库项目(或者其他非Web项目中)有时候需要HttpContext.Current这个方法,我们发现在类里面添加了引用“using System.Web;”之后还是不行,其实解解这个问题很简单,只需要在该项目的“引用”中添加System.Web这个引用就可以了。 另外值得注意的是,在非Web项目中使用HttpContext.Current.Cache、HttpContext.Current.Session等的时候,最好进行判断HttpContext.Current是否为空: if (HttpContext.Current != null && HttpContext.Current.Session != null) { string test = HttpContext.Current.Session[“Session”].ToString(); } 这是因为有些情况下Session或者Cache等会被截断,比如在.ashx文件中,默认情况下就会截断Session。当然也可以通过设置在.ashx文件中使用Session,但是为了安全,最后进行判断。 如果要在.ashx文件中使用Session,那么要先引用“using System.Web.SessionState;”,然后继承接口“IRequiresSessionState”,下面是一个例子: using System; using System.Data; using System.Web; using System.Collections; using System.Web.Services; using System.Web.Services.Protocols; using System.Web.SessionState; namespace Lemon.Life.WebData {/// 演示“在.ashx中使用Session” [WebService(Namespace = “http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class Xml : IHttpHandler, IRequiresSessionState { public void ProcessRequest(HttpContext context) { context.Response.ContentType = “text/plain”; context.Session[“Test”] = “Test”; string test = context.Session[“Test”].ToString(); context.Response.Write(“Hello World”); } public bool IsReusable { get { return false;} } } }

May 21, 2012

.NET的Windows模拟身份验证,远程网络传输文件

在开发过程中,常常遇到这样的问题:文件,包括图片和文件上传到服务器,而Web服务器和文件服务器不是同一个,而且不在同一个域里面,那么针对于.NET应该如何处理这样的问题呢? 可能很多高手一下子就知道如何解决,但是我确实是经过了一番努力才弄明白,下面就结束如何使用.NET的Windows模拟身份验证。 1、首先引用两个名称空间 using System.Security.Principal; using System.Runtime.InteropServices; 2、其次定义好模拟权限的调用方法 region 权限模拟 public const int LOGON32_LOGON_INTERACTIVE = 2; public const int LOGON32_PROVIDER_DEFAULT = 0; [DllImport("advapi32.dll", CharSet = CharSet.Auto)] public static extern int LogonUser(String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); [DllImport("advapi32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)] public static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);<summary>/// 验证用户,并生成WindowsIdentity 实例 </summary>private static WindowsIdentity GetIdentity(String userName, String domain, String password) { IntPtr token = IntPtr.Zero; IntPtr tokenDuplicate = IntPtr.Zero; if (LogonUser(userName, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) == 0) return null; else if (DuplicateToken(token, 2, ref tokenDuplicate) == 0) return null; else return new WindowsIdentity(tokenDuplicate); } public WindowsImpersonationContext GetContext() { WindowsIdentity identity = null; WindowsImpersonationContext impersonationContext = null; identity = string.IsNullOrEmpty(user) ? null : GetIdentity(user, null, pwd); // 使用用户凭证进行用户模拟 impersonationContext = (identity == null) ? null : identity.Impersonate(); return impersonationContext; } #endregion 3、在调用身份模拟的函数中使用 ...

May 21, 2011

设置Session永不过期,Session不过期

很多时候为了需要,必须使用Session,但是Session过期问题一直困扰很多人,我也是。通过网上的搜索,发现了可以通过两种方式了设置,但是只有一个可以永不过期。 保持Session的方法: (1)、设Session.timeout=-1,或小于0的数。这种方法肯定是不行的,session计算时间以分钟为单位,必须是大于等于1的整数。 (2)、设Session.timeout=99999。这种同样不行,Session有最大时间限制。 其实,Session最大值为24小时,也就是说你最大可以Session.timeout=1440,1441都是不可以有。 所以想通过设Session.timeout的过期时间让Session永不过期是不可能的。写到Cookies里是比较好的方法,但是Cookie非常的危险,如果在外面的电脑很容易被别人劫持,不安全! 那么有没有一种方法可以保持Session呢,可以使用一种办法,就是在要保持session的页里设隐藏iframe 这个Iframe每隔一段时间刷新一次这个Iframe页面就可以了。

May 21, 2011