上一章讲解了软件设计中主要用到的三个设计模型,本节讲解三个服务。等咱们这次都讲完了再最后进行一次归纳,即:系统开发流程中的三模型、软件设计中的三模型和三个服务,我习惯管这个叫3*3*3。看完了您就会知道我为什么常说软件设计这活是朴素的,没那么多弯弯绕,只是因为我们在学习过程中没有做思考和归纳。设计模式的那四个哥们儿不也是根据其经验总结出了流传至今且经久不衰的23个模式(其实常用的也没几个)以及6个原则。这里还是要再多说两句,23个设计模式其实属于代码设计阶段,应该是每个软件工程师必须掌握的一门技术。但,如果是初学者千万别刻意在产品中去用,强拧的瓜不甜,用完了搞不好还被领导骂你傻缺。我个人写代码时间久了,所以遇到一些典型的场景会直接使用某个模式,非典型的一般是在重构时决定是否有合适的模式供使用。另外,还有一些企业级软件的设计模式也需要去学习的,比如:熔断器、IoC、工作单元等。
书归正传,一提到“服务”,您肯定会想:这简单啊,一个类里面没有字段只有方法就可以称其为服务。这样的定义太大且模糊,我们既然要学习设计就把这个东西整细了。也就是要总结一下到底有哪些服务,每个服务的作用是什么,应用于什么场景,这才叫系统化学习。首先一点,服务的定义说白了的确就是“只有方法没有字段的类”,这是使用服务的一种常用模式。也还有一些特别的,比如计数器服务类里面可能有“AtomicLong”类型的字段标识计数值。一般来说,服务顶好是不要包含字段且单例的,即使加了也尽量从技术角度做好并发访问控制;第二,好的服务每个接口都可以提供一个完整的功能;第三,服务主要是用于提供特定的功能,其代码一般是以面向过程为主。
上面对服务进行了一个白话版的定义,精确度略显不足,不过本系列文章也不是站在高大上的角度来教化读者的。谈完了定义再说说服务的类型,软件设计过程中主要包含三类服务:领域服务、应用服务和数据服务,具体如下图所示。

如果您学习过软件工程这本书,会发现在讨论设计相关的知识的时总是提一个词“控制类”。遥想当年,我在上这门课的时候对好多的词都不懂,比如“数据字典”、“控制类”、“用例”等,听着就不明觉厉,但实际上对这些东西完全没有什么概念,不过考试及格罢了。工作几年后,做了一堆系统,还是不太明白所谓的“控制类”到底是何方神圣。这个事儿说起来挺玄幻的,再后来就突然“顿悟”了,也可能是看资料看得多了突然有所感应。接触过DDD的您肯定知道有一个概念叫“应用服务”,再不济面向过程的代码您总会写过吧?里面那个“service”就是应用服务,也就是所谓的“控制类”。
说到这儿您肯定明白了应用服务的作用了吧?后面我们会详细说明,在这里面只给出一个概念性的解释。应用服务的主要目的是控制一个事务内的(加紧拿小本本儿记下来,重点)业务流程的运转,先干啥后干啥全靠它来搞,是业务的入口。从UML的角度来看,一般是一个用例对应一个控制类的接口。我们在设计领域模型的时候限制较多比如不能访问基础设施,应用服务没这个限制,几乎可以随便玩儿,可能最重要的且强制性的要求就是只能访问其它包的应用服务,不能再向内进行入侵。需要注意的是不论您是用面向过程设计还是面向对象设计,控制类的代码永远是面向过程的。除了访问控制,应用服务的使用和设计还有许多的规范比如日志、返回值、异常处理等,后面会细聊。
数据服务就是DAO,用于执行数据的持久化与反持久化,这东西全宇宙都知道,也没什么可讲的。需要注意的是DAO不仅仅是用于MySQL这种关系型数据库,涉及其它非关系型如Redis、MongoDB的操作都需要在DAO内搞定,别一会儿放应用服务内,一会儿放DAO内。既然说到这儿了,我给您展示一下如何在DAO内同时操作MySQL和Redis。我这里的DAO基于MyBatis框架,使用了接口来与配置文件对应。上面说了,Redis操作也算是DAO内的东西,但现在的DAO是个接口,不能写代码的,so……ladies and gentlemen,请看示例。
public interface DictionaryMapper extends GenericDao<DictionaryDataEntity, Integer> {
/**
* 根据类别ID查询数据字典
* @param classId 类别ID
* @return 数据字典列表
*/
List<DictionaryDataEntity> selectByClassId(Integer classId);
/**
* 根据查询条件查询数据字典
* @param criteria 查询条件
* @return 数据字典列表
*/
List<DictionaryDataEntity> selectAll(DictionaryCriteria criteria);
}
@Repository
public class DictionaryDaoExtension implements DictionaryMapper {
@Resource
private RedisCacheUtils redisCacheUtils;
@Resource
private DictionaryMapper dictionaryMapper;
@Override
public List<DictionaryDataEntity> selectByClassId(Integer classId) {
if (classId == null) {
return new ArrayList<>();
}
RedisCacheUtils.RedisKey redisKey = this.buildRedisKey();
String cached = this.redisCacheUtils.getHash(redisKey, classId);
if (!StringUtils.isEmpty(cached)) {
return JsonUtils.fromJsonToList(cached, DictionaryDataEntity.class);
}
List<DictionaryDataEntity> result = this.dictionaryMapper.selectByClassId(classId);
if (result != null && !result.isEmpty()) {
RedisCacheUtils.RedisKeyLifeCycle lifeCycle =
new RedisCacheUtils.RedisKeyLifeCycle(CACHE_TIME_OUT, TimeUnit.DAYS);
this.redisCacheUtils.setHashValue(redisKey, classId.toString(), JsonUtils.toJson(result), lifeCycle);
}
return result;
}
@Override
public DictionaryDataEntity getById(Integer id) throws DataAccessException {
return this.dictionaryMapper.getById(id);
}
@Override
public int deleteById(Integer id) throws DataAccessException {
throw new UnsupportedOperationException();
}
……
}
这里的“DictionaryDaoExtension”使用了一个装饰模式,继承于“DictionaryMapper”并包含了一个“DictionaryMapper”类型的实例,“selectByClassId”方法中加上了缓存相关的操作。
领域服务是DDD战术阶段中定义的一个重要模型,当某个方法无法区分其属于哪个领域实体的时候一般会放到领域服务中。此外,如果一个方法涉及多个模型也就是所谓的跨模型操作,一般也会放到领域服务中。这里面需要注意的是领域服务和领域模型是一样的,最好只依赖于JDK。我见过一些设计,作者将“资源库(Repository)”注入到领域服务或领域模型中,个人比较不推荐这种方式,与Spring框架耦合过于严重了。
在继续讲之前还需要说一下所谓的面向对象编程(OOP)到底是什么东西。作为对比,我们先说一下面向过程,简单来说就是根据业务流程说明一行一行的写代码最终完成整个用例,比较直观和简单。OOP如果用白话去说就是:在一个业务场景(用例)中,把涉及到的对象全拿出来,每个对象执行属于自己责任的任务。一般来说,需要通过应用服务来控制各对象的行为。之所以在领域服务中介绍这一段内容,是因为有一种设计模式:为每一个用例都建立对应的领域服务,应用服务不再直接调用领域模型而是转面调用此领域服务,再由后者调用领域模型。现实情况中大部分用例在每个子事务(一个用例可能涉及多个事务,使用最终一致性)中只会有一个领域模型参与,所以具体如何使用看个人习惯以及是否有必要。
前后端分离和微服务架构已成为当前主流的设计方式,RESTfu、Web API或其它RPC是前后两端以及微服务间交互的主要手段。所以在我们的开发过程中往往会设计一些适配器组件比如“rest”层用于将当前系统的能力以RESTful接口的方式提供出去。虽然“rest”层的代码比较符合服务的定义,但一般并不会将其视为服务来看,其属于适配器(可参看六边型架构。严格上来讲DAO的实现也是一种适配器,不过鉴于其在开发过程中的戏份较多,我将其看作一种服务来对待)的一种,除了用于实现REST能力几乎不包含任何业务或数据逻辑,更多的是透传操作,类似的还包括各类工具类等。
三个服务讲完了,虽然软件设计中可能还包含各种其它的“类服务”组件,但由于分量不足达不到主角的层次,撑死了是个二线配角,没流量。现在您仔细品味一下,会发现在开发过程中您所主要涉及的除了模型就是服务,没其它的了。那是不是说开发其实就已经有了一个整体的思路或模式了呢?比如“先设计模型,后设计服务”这种?这属于我个人的总结,您也许会有自己专用的模式。想要开发效率咱不能无脑的干,也得想想是否有现成儿的最佳实践或总结出适合于自己的方式,这叫态度。
结合前面战术部分所讲的内容,我们总结出来在软件开发与设计流程中会涉及9种对象,这9种对象几乎涵盖了系统中的方方面面。各对象的概念前面已经做了详细介绍,下面的列表仅展示定义。
虽然重复,在这里仍然把前面所用的图总结性的贴出来供参考。

本章主要讲了3个服务的概念和软件开发流程中所涉及的9类对象。后面的内容中还会对部分内容做细讲。其实很多的概念您仔细看都特别简单,而我的只是把这些内容进行了总结归纳。最重要的是让我们的开发有据可循、有理可循,不能盲目的进行。
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..
我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数
最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru
在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo
我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b
您如何在Rails中的实时服务器上进行有效调试,无论是在测试版/生产服务器上?我试过直接在服务器上修改文件,然后重启应用,但是修改好像没有生效,或者需要很长时间(缓存?)我也试过在本地做“脚本/服务器生产”,但是那很慢另一种选择是编码和部署,但效率很低。有人对他们如何有效地做到这一点有任何见解吗? 最佳答案 我会回答你的问题,即使我不同意这种热修补服务器代码的方式:)首先,你真的确定你已经重启了服务器吗?您可以通过跟踪日志文件来检查它。您更改的代码显示的View可能会被缓存。缓存页面位于tmp/cache文件夹下。您可以尝试手动删除
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭10年前。问题1)我想知道rubyonrails是否有功能类似于primefaces的gem。我问的原因是如果您使用primefaces(http://www.primefaces.org/showcase-labs/ui/home.jsf),开发人员无需担心javascript或jquery的东西。据我所知,JSF是一个规范,基于规范的各种可用实现,prim
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit