wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/7u79-b15/jdk-7u79-linux-x64.rpm
文件会被下载到当前文件夹。
wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/7u79-b15/jdk-7u79-linux-x64.rpm
文件会被下载到当前文件夹。
单元测试和集成测试在我们的软件开发整个流程中占有举足轻重的地位,一方面,程序员通过编写单元测试来验证自己程序的有效性,另外一方面,管理者通过持续自动的执行单元测试和分析单元测试的覆盖率等来确保软件本身的质量。这里,我们先不谈单元测试本身的重要性,对于目前大多数的基于 Java 的企业应用软件来说,Spring 已经成为了标准配置,一方面它实现了程序之间的低耦合度,另外也通过一些配置减少了企业软件集成的工作量,例如和 Hibernate、Struts 等的集成。那么,有个问题,在普遍使用 Spring 的应用程序中,我们如何去做单元测试?或者说,我们怎么样能高效的在 Spring 生态系统中实现各种单元测试手段?这就是本文章要告诉大家的事情。
单元测试目前主要的框架包括 Junit、TestNG,还有些 MOCK 框架,例如 Jmock、Easymock、PowerMock 等,这些都是单元测试的利器,但是当把他们用在 Spring 的开发环境中,还是那么高效么?还好,Spring 提供了单元测试的强大支持,主要特性包括:
通过阅读本文,您能够快速的掌握基于 Spring TestContext 框架的测试方法,并了解基本的实现原理。本文将提供大量测试标签的使用方法,通过这些标签,开发人员能够极大的减少编码工作量。OK,现在让我们开始 Spring 的测试之旅吧!
这里先展示一个基于 Junit 的单元测试,这个单元测试运行在基于 Spring 的应用程序中,需要使用 Spring 的相关配置文件来进行测试。相关类图如下:
假设有一个员工账号表,保存了员工的基本账号信息,表结构如下:
假设表已经建好,且内容为空。
在 Eclipse 中,我们可以展开工程目录结构,看到如下图所示的工程目录结构和依赖的 jar 包列表:

假设我们现在有一个基于 Spring 的应用程序,除了 MVC 层,还包括业务层和数据访问层,业务层有一个类 AccountService,负责处理账号类的业务,其依赖于数据访问层 AccountDao 类,此类提供了基于 Spring Jdbc Template 实现的数据库访问方法,AccountService 和 AccountDao 以及他们之间的依赖关系都是通过 Spring 配置文件进行管理的。
现在我们要对 AccountService 类进行测试,在不使用 Spring 测试方法之前,我们需要这样做:
此类代表账号的基本信息,提供 getter 和 setter 方法。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
package domain; public class Account { public static final String SEX_MALE = "male"; public static final String SEX_FEMALE = "female"; private int id; private String name; private int age; private String sex; public String toString() { return String.format("Account[id=%d,name=%s,age:%d,sex:%s]",id,name,age,sex); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public static Account getAccount(int id,String name,int age,String sex) { Account acct = new Account(); acct.setId(id); acct.setName(name); acct.setAge(age); acct.setSex(sex); return acct; } } |
注意上面的 Account 类有一个 toString() 方法和一个静态的 getAccount 方法,getAccount 方法用于快速获取 Account 测试对象。
这个 DAO 我们这里为了简单起见,采用 Spring Jdbc Template 来实现。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
package DAO; import Java.sql.ResultSet; import Java.sql.SQLException; import Java.util.HashMap; import Java.util.List; import Java.util.Map; import org.Springframework.context.ApplicationContext; import org.Springframework.context.support.ClassPathXmlApplicationContext; import org.Springframework.jdbc.core.RowMapper; import org.Springframework.jdbc.core.namedparam.NamedParameterJdbcDaoSupport; import org.Springframework.jdbc.core.simple.ParameterizedRowMapper; import domain.Account; public class AccountDao extends NamedParameterJdbcDaoSupport { public void saveAccount(Account account) { String sql = "insert into tbl_account(id,name,age,sex) " + "values(:id,:name,:age,:sex)"; Map paramMap = new HashMap(); paramMap.put("id", account.getId()); paramMap.put("name", account.getName()); paramMap.put("age", account.getAge()); paramMap.put("sex",account.getSex()); getNamedParameterJdbcTemplate().update(sql, paramMap); } public Account getAccountById(int id) { String sql = "select id,name,age,sex from tbl_account where id=:id"; Map paramMap = new HashMap(); paramMap.put("id", id); List<Account> matches = getNamedParameterJdbcTemplate().query(sql, paramMap,new ParameterizedRowMapper<Account>() { @Override public Account mapRow(ResultSet rs, int rowNum) throws SQLException { Account a = new Account(); a.setId(rs.getInt(1)); a.setName(rs.getString(2)); a.setAge(rs.getInt(3)); a.setSex(rs.getString(4)); return a; } }); return matches.size()>0?matches.get(0):null; } } |
AccountDao 定义了几个账号对象的数据库访问方法:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package service; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.Springframework.beans.factory.annotation.Autowired; import DAO.AccountDao; import domain.Account; public class AccountService { private static final Log log = LogFactory.getLog(AccountService.class); @Autowired private AccountDao accountDao; public Account getAccountById(int id) { return accountDao.getAccountById(id); } public void insertIfNotExist(Account account) { Account acct = accountDao.getAccountById(account.getId()); if(acct==null) { log.debug("No "+account+" found,would insert it."); accountDao.saveAccount(account); } acct = null; } } |
AccountService 包括下列方法:
其依赖的 DAO 对象 accountDao 是通过 Spring 注释标签 @Autowired 自动注入的。
上述几个类的依赖关系是通过 Spring 进行管理的,配置文件如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<beans xmlns="http://www.Springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.Springframework.org/schema/context"xsi:schemaLocation="http://www.Springframework.org/schema/beans http://www.Springframework.org/schema/beans/Spring-beans-3.0.xsd http://www.Springframework.org/schema/context http://www.Springframework.org/schema/context/Spring-context-3.0.xsd "> <context:annotation-config/> <bean id="datasource" class="org.Springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="org.hsqldb.jdbcDriver" /> <property name="url" value="jdbc:hsqldb:hsql://localhost" /> <property name="username" value="sa" /> <property name="password" value="" /> </bean> <bean id="initer" init-method="init" class="service.Initializer"> </bean> <bean id="accountDao" depends-on="initer" class="DAO.AccountDao"> <property name="dataSource" ref="datasource" /> </bean> <bean id="accountService" class="service.AccountService"> </bean> </beans> |
注意其中的“<context:annotation-config/>”的作用,这个配置启用了 Spring 对 Annotation 的支持,这样在我们的测试类中 @Autowired 注释才会起作用(如果用了 Spring 测试框架,则不需要这样的配置项,稍后会演示)。另外还有一个 accountDao 依赖的 initer bean, 这个 bean 的作用是加载 log4j 日志环境,不是必须的。
另外还有一个要注意的地方,就是 datasource 的定义,由于我们使用的是 Spring Jdbc Template,所以只要定义一个 org.Springframework.jdbc.datasource.DriverManagerDataSource 类型的 datasource 即可。这里我们使用了简单的数据库 HSQL、Single Server 运行模式,通过 JDBC 进行访问。实际测试中,大家可以选择 Oracle 或者 DB2、Mysql 等。
好,万事具备,下面我们来用 Junit4 框架测试 accountService 类。代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
package service; import static org.Junit.Assert.assertEquals; import org.Junit.BeforeClass; import org.Junit.Test; import org.Springframework.context.ApplicationContext; import org.Springframework.context.support.ClassPathXmlApplicationContext; import domain.Account; public class AccountServiceOldTest { private static AccountService service; @BeforeClass public static void init() { ApplicationContext context = new ClassPathXmlApplicationContext("config/Spring-db-old.xml"); service = (AccountService)context.getBean("accountService"); } @Test public void testGetAcccountById() { Account acct = Account.getAccount(1, "user01", 18, "M"); Account acct2 = null; try { service.insertIfNotExist(acct); acct2 = service.getAccountById(1); assertEquals(acct, acct2); } catch (Exception ex) { fail(ex.getMessage()); } finally { service.removeAccount(acct); } } } |
注意上面的 Junit4 注释标签,第一个注释标签 @BeforeClass,用来执行整个测试类需要一次性初始化的环境,这里我们用 Spring 的 ClassPathXmlApplicationContext 从 XML 文件中加载了上面定义的 Spring 配置文件,并从中获得了 accountService 的实例。第二个注释标签 @Test 用来进行实际的测试。
测试过程:我们先获取一个 Account 实例对象,然后通过 service bean 插入数据库中,然后通过 getAccountById 方法从数据库再查询这个记录,如果能获取,则判断两者的相等性;如果相同,则表示测试成功。成功后,我们尝试删除这个记录,以利于下一个测试的进行,这里我们用了 try-catch-finally 来保证账号信息会被清除。
执行测试:(在 Eclipse 中,右键选择 AccountServiceOldTest 类,点击 Run as Junit test 选项),得到的结果如下:
在 Eclipse 的 Junit 视图中,我们可以看到如下的结果:

对于这种不使用 Spring test 框架进行的单元测试,我们注意到,需要做这些工作:
另外,在这个测试类中,我们还不能使用 Spring 的依赖注入特性。一切都靠手工编码实现。好,那么我们看看 Spring test 框架能做到什么。
首先我们修改一下 Spring 的 XML 配置文件,删除 <context:annotation-config/> 行,其他不变。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<beans xmlns="http://www.Springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.Springframework.org/schema/beans http://www.Springframework.org/schema/beans/Spring-beans-3.2.xsd"> <bean id="datasource"class="org.Springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="org.hsqldb.jdbcDriver" /> <property name="url" value="jdbc:hsqldb:hsql://localhost" /> <property name="username" value="sa"/> <property name="password" value=""/> </bean> <bean id="transactionManager"class="org.Springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="datasource"></property> </bean> <bean id="initer" init-method="init" class="service.Initializer"> </bean> <bean id="accountDao" depends-on="initer" class="DAO.AccountDao"> <property name="dataSource" ref="datasource"/> </bean> <bean id="accountService" class="service.AccountService"> </bean> </beans> |
其中的 transactionManager 是 Spring test 框架用来做事务管理的管理器。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
package service; import static org.Junit.Assert.assertEquals; import org.Junit.Test; import org.Junit.runner.RunWith; import org.Springframework.beans.factory.annotation.Autowired; import org.Springframework.test.context.ContextConfiguration; import org.Springframework.test.context.Junit4.SpringJUnit4ClassRunner; import org.Springframework.transaction.annotation.Transactional; import domain.Account; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("/config/Spring-db1.xml") @Transactional public class AccountServiceTest1 { @Autowired private AccountService service; @Test public void testGetAcccountById() { Account acct = Account.getAccount(1, "user01", 18, "M"); service.insertIfNotExist(acct); Account acct2 = service.getAccountById(1); assertEquals(acct,acct2); } } |
对这个类解释一下:
在 Eclipse 的 Junit 视图中,我们可以看到如下的结果:

如果您希望在 Spring 环境中进行单元测试,那么可以做如下配置:
另外您不再需要:
我们已经看到利用 Spring test framework 来进行基于 Junit4 的单元测试是多么的简单,下面我们来看一下前面遇到的各种注释标签的一些可选用法。
刚才已经介绍过,可以输入 Spring xml 文件的位置,Spring test framework 会自动加载 XML 文件,得到 application context,当然也可以使用 Spring 3.0 新提供的特性 @Configuration,这个注释标签允许您用 Java 语言来定义 bean 实例,举个例子:
现在我们将前面定义的 Spring-db1.xml 进行修改,我们希望其中的三个 bean:initer、accountDao、accountService 通过配置类来定义,而不是 XML,则我们需要定义如下配置类:
注意:如果您想使用 @Configuration,请在 classpath 中加入 cglib 的 jar 包(cglib-nodep-2.2.3.jar),否则会报错。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package config; import org.Springframework.beans.factory.annotation.Autowired; import org.Springframework.context.annotation.Bean; import org.Springframework.context.annotation.Configuration; import org.Springframework.jdbc.datasource.DriverManagerDataSource; import service.AccountService; import service.Initializer; import DAO.AccountDao; @Configuration public class SpringDb2Config { private @Autowired DriverManagerDataSource datasource; @Bean public Initializer initer() { return new Initializer(); } @Bean public AccountDao accountDao() { AccountDao DAO = new AccountDao(); DAO.setDataSource(datasource); return DAO; } @Bean public AccountService accountService() { return new AccountService(); } } |
注意上面的注释标签:
注意,我们采用的是 XML+config bean 的方式进行配置,这种方式比较符合实际项目的情况。相关的 Spring 配置文件也要做变化,如下清单所示:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<beans xmlns="http://www.Springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.Springframework.org/schema/context"xsi:schemaLocation="http://www.Springframework.org/schema/beans http://www.Springframework.org/schema/beans/Spring-beans-3.0.xsd http://www.Springframework.org/schema/context http://www.Springframework.org/schema/context/Spring-context-3.0.xsd"> <context:annotation-config/> <bean id="datasource"class="org.Springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="org.hsqldb.jdbcDriver" /> <property name="url" value="jdbc:hsqldb:hsql://localhost" /> <property name="username" value="sa"/> <property name="password" value=""/> </bean> <bean id="transactionManager" class="org.Springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="datasource"></property> </bean> <bean class="config.SpringDb2Config"/> </beans> |
注意里面的 context 命名空间的定义,如代码中黑体字所示。另外还必须有 <context:annotaiton-config/> 的定义,这个定义允许采用注释标签的方式来控制 Spring 的容器,最后我们看到 beans 已经没有 initer、accountDao 和 accountService 这些 bean 的定义,取而代之的是一个 SpringDb2Config bean 的定义,注意这个 bean 没有名称,因为不需要被引用。
现在有了这些配置,我们的测试类只要稍稍修改一下,即可实现加载配置类的效果,如下:
|
1
|
@ContextConfiguration("/config/Spring-db2.xml") |
通过上面的配置,测试用例就可以实现加载 Spring 配置类,运行结果也是成功的 green bar。
缺省情况下,Spring 测试框架一旦加载 applicationContext 后,将一直缓存,不会改变,但是,
由于 Spring 允许在运行期修改 applicationContext 的定义,例如在运行期获取 applicationContext,然后调用 registerSingleton 方法来动态的注册新的 bean,这样的情况下,如果我们还使用 Spring 测试框架的被修改过 applicationContext,则会带来测试问题,我们必须能够在运行期重新加载 applicationContext,这个时候,我们可以在测试类或者方法上注释:@DirtiesContext,作用如下:
缺省情况下,Spring 测试框架将事务管理委托到名为 transactionManager 的 bean 上,如果您的事务管理器不是这个名字,那需要指定 transactionManager 属性名称,还可以指定 defaultRollback 属性,缺省为 true,即所有的方法都 rollback,您可以指定为 false,这样,在一些需要 rollback 的方法,指定注释标签 @Rollback(true)即可。
看了上面 Spring 测试框架的注释标签,我们来看看一些常见的基于 Junit4 的注释标签在 Spring 测试环境中的使用方法。
此注释标签的含义是,这是一个测试,期待一个异常的发生,期待的异常通过 xxx.class 标识。例如,我们修改 AccountService.Java 的 insertIfNotExist 方法,对于传入的参数如果为空,则抛出 IllegalArgumentException,如下:
|
1
2
3
4
5
6
7
8
9
10
|
public void insertIfNotExist(Account account) { if(account==null) throw new IllegalArgumentException("account is null"); Account acct = accountDao.getAccountById(account.getId()); if(acct==null) { log.debug("No "+account+" found,would insert it."); accountDao.saveAccount(account); } acct = null; } |
然后,在测试类中增加一个测试异常的方法,如下:
|
1
2
3
4
|
@Test(expected=IllegalArgumentException.class) public void testInsertException() { service.insertIfNotExist(null); } |
运行结果是 green bar。
可以给测试方法指定超时时间(毫秒级别),当测试方法的执行时间超过此值,则失败。
比如在 AccountService 中增加如下方法:
|
1
2
3
4
5
6
|
public void doSomeHugeJob() { try { Thread.sleep(2*1000); } catch (InterruptedException e) { } } |
上述方法模拟任务执行时间 2 秒,则测试方法如下:
|
1
2
3
4
|
@Test(timeout=3000) public void testHugeJob() { service.doSomeHugeJob(); } |
上述测试方法期待 service.doSomeHugeJob 方法能在 3 秒内结束,执行测试结果是 green bar。
通过 @Repeat,您可以轻松的多次执行测试用例,而不用自己写 for 循环,使用方法:
|
1
2
3
4
5
|
@Repeat(3) @Test(expected=IllegalArgumentException.class) public void testInsertException() { service.insertIfNotExist(null); } |
这样,testInsertException 就能被执行 3 次。
从 Spring 3.2 以后,Spring 开始支持使用 @ActiveProfiles 来指定测试类加载的配置包,比如您的配置文件只有一个,但是需要兼容生产环境的配置和单元测试的配置,那么您可以使用 profile 的方式来定义 beans,如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
<beans xmlns="http://www.Springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.Springframework.org/schema/beans http://www.Springframework.org/schema/beans/Spring-beans-3.2.xsd"> <beans profile="test"> <bean id="datasource" class="org.Springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="org.hsqldb.jdbcDriver" /> <property name="url" value="jdbc:hsqldb:hsql://localhost" /> <property name="username" value="sa"/> <property name="password" value=""/> </bean> </beans> <beans profile="production"> <bean id="datasource" class="org.Springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="org.hsqldb.jdbcDriver" /> <property name="url" value="jdbc:hsqldb:hsql://localhost/prod" /> <property name="username" value="sa"/> <property name="password" value=""/> </bean> </beans> <beans profile="test,production"> <bean id="transactionManager" class="org.Springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="datasource"></property> </bean> <bean id="initer" init-method="init" class="service.Initializer"> </bean> <bean id="accountDao" depends-on="initer" class="DAO.AccountDao"> <property name="dataSource" ref="datasource"/> </bean> <bean id="accountService" class="service.AccountService"> </bean> <bean id="envSetter" class="EnvSetter"/> </beans> </beans> |
上面的定义,我们看到:
|
1
2
3
4
5
6
7
|
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("/config/Spring-db.xml") @Transactional @ActiveProfiles("test") public class AccountServiceTest { ... } |
注意上面的 @ActiveProfiles,可以指定一个或者多个 profile,这样我们的测试类就仅仅加载这些名字的 profile 中定义的 bean 实例。
Spring 2.5 以后,就开始支持 TestNG 了,支持的方法包括:
这里我们演示一下如何使用 Spring 提供的 TestNG 父类来进行测试。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package testng; import static org.Junit.Assert.assertEquals; import org.Springframework.beans.factory.annotation.Autowired; import org.Springframework.test.context.ActiveProfiles; import org.Springframework.test.context.ContextConfiguration; import org.Springframework.test.context.testng. AbstractTransactionalTestNGSpringContextTests; import org.Springframework.transaction.annotation.Transactional; import service.AccountService; import domain.Account; @ContextConfiguration("/config/Spring-db.xml") @Transactional @ActiveProfiles("test") public class AccountServiceTestNGTest extends AbstractTransactionalTestNGSpringContextTests { @Autowired private AccountService service; @org.testng.annotations.Test public void testGetAcccountById() { Account acct = Account.getAccount(1, "user01", 18, "M"); service.insertIfNotExist(acct); Account acct2 = service.getAccountById(1); assertEquals(acct,acct2); } } |
执行测试,我们将看到测试成功。

搜索数据库对应的表,我们看到里面没有数据,说明自动事务起作用了。
Spring test framework 主要位于 org.Springframework.test.context 包中,主要包括下面几个类:

Spring 通过 AOP hook 了测试类的实例创建、beforeClass、before、after、afterClass 等事件入口,执行顺序主要如下:

根据上面的例子和介绍,我们可以看到,Spring 测试框架的主要特点如下:
总之,如果您的程序中使用了 Spring,且对用 Junit 或者 testNG 来对他们进行单元测试感到力不从心,可以考虑使用 Spring test framework,它将使您的应用程序的质量上一个新的台阶。
前言
从15年毕业离校,至17年初,一直在传统开发领域做开发,一个应用从前端后台还有app等集合成一块,然后发布到运维,整个一条线。17年元旦开始,开始负责深圳千万级别的项目的技术构架,从传统领域走向微服务开发,从整个业务流程的转换到整个开发流程的确认,从开发的流水线到自动集成发布,整个过程完全走产品化路线,集成化路线。
为了更好的做好讲解,本文将对校园问答平台的开发做一次技术性解析,从技术选型到发布的整个过程。
技术选型:
开发工具:JDK8、STS(Eclipse),Maven+Nexus;
主要技术栈:
服务注册与发现:Dubbo、Zookeeper;
缓存 : Redis
Http容器:Jetty
版本控制:SVN(淘宝svn)
异步消息:Kafka
前端:bootstrap3
持续集成平台:Jenkins
选型的技术应该常见,这是在某一角度上来说,并不是越新的技术越好,或者听起来越高大上的技术即是完美,具体按业务场景。
数据库设计
数据库使用mysql,单表设计,数据库脱离第三范式,将业务逻辑移至应用层,多冗余设计。
基本字段:
e_id 全局主键
e_env 全局环境
c_id 容器的id
t_id 商户的id
app_id 应用的id
全局字段的意思即全局数据库引用此数据,便于后期其它应用接入做扩展处理。
服务模块划分:
为了更接近实际设计场景,这里做了大概规划,
基础服务,
在介绍这个过程之前,先强调一个观点:
Jira原是设计来进行Bug跟踪的系统,后来系统功能逐步完善后,被广泛适用于软件过程管理。Jira优势在于简单,好用。 这里就不介绍Jira的具体使用。 使用Jira进行软件项目管理,首先需要定义任务的处理流程。 以下是一个参考流程:
在这个流程中,需要区分两个概念:任务和子任务。 每个任务对应一个完整的业务需求,比如对账、对接工行借记卡、获取个人优惠券列表接口。这些业务需求每个都是可以独立测试的。子任务设置相对比较简单,每个子任务对应这在本次任务执行中需要修改的开发项目。 比如对接工行借记卡,会涉及到:
三个项目的修改,那会对应在这个任务下建立三个子任务。
这样,针对任务和子任务,会设置不同的属性:
Jira也是一个不错的需求管理工具。产品经理可以通过Jira来执行需求管理,相对开发来说,需求管理流程会比较简单,一般是开发需求、审核需求、关闭需求三个环节即可。 需要注意的地方是:
一个需求任务可以对应多个开发任务,这在实际操作中是很常见的:
如上所述,开发任务的来源有两个:
那任务的粒度如何把握? 每个开发任务是一个完整的需求,是可以独立执行测试和验证的。 每个任务开发周期控制在1个月以内。
在接收到开发任务后,开发人员需要对系统实现进行设计和分解,确定需要新开发的内容以及需要改进的工作。 在微服务架构中,一次任务开发会涉及到多个系统的变更。这样就需要为每个系统建立一个独立的子任务,以后,我们将按照这个子任务的设置来驱动开发流程。 每个子任务开发周期尽量限制2天以内,不能超过一周。
主任务启动开发流程比较简单,主要是邮件通知到各相关人员,可以启动该任务。
子任务的启动和执行,是整个流程的核心工作。
这里如果是使用git/gitlab来做版本控制,整个流程的要点在于:
子任务开发完成后,即可提测。子任务提测时,将触发Jenkins进行测试环境部署。
测试有两种方式:自动测试和人工测试。尽量采用自动测试,使得开发人员能够及时发现问题。
所有子任务完成后,主任务可以提测。主任务提测后,如果是人工测试,则测试人员介入开始执行测试任务;如果是自动测试,则开始运行集成测试脚本。
测试通过后, 既可以准备上线。
一般上线会分为两步,预部署和全部署。预部署的目的是先验证系统在线上环境运行是否正常,减少回滚成本。特别是在部署服务器特别多的情况下,先部署1-2台机器,可以在线上验证本次上线是否可以。 验证通过后,既可以执行全部署。
注意,预部署和全部署都是针对子任务而言。
少数公司会要求上线前进行审批,但这样做是不利于流程自动化的。 一天几十次上线,谁能知道这是不是可以上。 但有一点很重要,系统上线前,必须通知到相关的使用方。如果出现问题,使用方可以尽快知悉。
开发参考目录结构:
从这个目录里面我们可以看到,和项目相关的部署用脚本,需要由项目开发人员自己来维护,用以保证部署工作能够自动执行。包括验证项目部署成功的脚本。
验证项目是否部署成功,一种方式是在日志中打桩,grep到这个日志,即意味着系统成功启动;一种方式是调用接口来验证是否成功。
部署目录参考:
总之,微服务项目的管理核心理念在于“自动化”,消除人为因素。人管代码,代码管机器,最终目标是要实现自动上线。 消除人工测试,取代以自动化测试;消除人工验证,取代以自动验证;消除人工部署,取代以自动化部署。 这样,再多的项目,也能够很好的进行管理。
答案不规范的话术:
1、答案不符合平台规范,请按规范提问,回答人员会按自己的经验分享给你,给你最好的经验。
如:民大校花是谁?
2、问题范围太广,背景略少,回答人员无法明确经验,建立百度得到更详细的答案。
如:如何独立?
3、重复提问,请在公众号搜索问题。
如:广西民族大学男女平台多少。
4、未明确指明学校,回答人员无法定位问题,无法给出个人经验,感谢对平台的支持。
如:为什么宿舍那么贵,却没有空调。