Scala微服务架构 一
Scala微服务架构 一
因为公司的一个项目需求变动会非常频繁,同时改动在可控范围内,加上产品同学喜欢一些超前思维,我们的CTO决定提前开启八门,使用微服务架构。
那么什么是微服务架构呢?
1.1 微服务架构
“微服务”架构是近期软件应用领域非常热门的概念。
1.1.1 什么是微服务架构
微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。
特点
也就是说,微服务就是SOA的变种,在保证SOA业务分离能力的同时,减少每个业务粒度,同时去除了SOA中比较恼人的ESB.
*组装
这个图片从资源利用率的角度,体现了微服务一个非常给力的优点
数据分离
同时,对应用组件封装的方式是整体架构与微服务架构的主要差异,微服务架构将相关联的业务逻辑及数据放在一起形成独立的边界,其目的是能在不影响其他应用组件(微服务)的情况下更快地交付并推出市场。
1.2 前后对接协议 JsonApi
1.3 本期语法糖
1.3.1 Scala的Package Object
官网讲解
为啥使用Package Object
而不是Object
呢?
原因很简单,答案是==代码更干净==。如何做到的呢?
我copy了官网的例子,诸位请看:
// in file gardening/fruits/Fruit.scala package gardening.fruits case class Fruit(name: String, color: String) object apple extends Fruit("Apple", "green") object plum extends Fruit("Plum", "blue") object banana extends Fruit("Banana", "yellow")
上面我在/gardening/fruits/
包下定义了几个Object
如果想使用直接使用上面定义的几个Object,可以这样
package com object main { import gardening.fruits._ val planted = List(apple, plum, banana) def showFruit(fruit: Fruit) { println(fruit.name +"s are "+ fruit.color) } }
但是,其实我们还有更简洁的调用方式,各位也已经猜到了,就是Package Object
,
只要在调用包的上一级/gardening/
包下定义Package Object fruits
,这样的写法就和class和objcet的伴生关系一样,我们可以直接访问伴生包中的变量,方法和隐式。
// in file gardening/fruits/package.scala package gardening package object fruits { val planted = List(apple, plum, banana) def showFruit(fruit: Fruit) { println(fruit.name +"s are "+ fruit.color) } }
这里要注意,思想不要局限,反过来也是可以的,比如在Package Object fruits
中定义了一系列的object,在/gardening/
包下的object可以直接使用。
1.3.2 Scala的自定义注解(Annotation)
Annotation (注解)的介绍
Scala中的注解语法与Java中类似。
标准库定义的注解相关内容在包scala.annotation中。
注解的基本语法为:
@注解名称(注解参数...) val a = ???
与Java注解的用法类似,注解参数不是必须的,一个元素允许拥有多个注解。
Annotation的定义
==Scala中的自定义注解不是接口/特质,而是类.==
自定义注解需要从注解特质中继承,Scala中提供了两类注解特质:
- scala.annotation.ClassfileAnnotation 由Java编译器生成注解
- scala.annotation.StaticAnnotation 由Scala编译器生成注解
两类注解特质都继承自基类scala.annotation.Annotation.两类注解特质的基类相同,因此自定义注解类时允许同时混入两类注解特质。
- 继承自ClassfileAnnotation基类的注解在使用时参数应以具名参数(named arguments)形式传入。
- 继承自StaticAnnotation基类的注解无此限制。
定义注解类语法与普通类相同:
// 标记注解 class 注解名称 extends StaticAnnotation/ClassfileAnnotation // 有参注解 class 注解名称(参数表...) extends StaticAnnotation/ClassfileAnnotation
Annotation的解析
通过反射机制获取注解信息,相关API位于scala.reflect.runtime.universe
包路径下。
获取注解:
- 获取类的注解:
- 使用typeOf()方法,获取Type类型的类信息。
typeOf[Test]: Type
- 通过Type.typeSymbol获取Symbol。
type.typeSymbol: Symbol
- 通过Symbol.annotations获取List[Annotation](注解列表)。
symbol.annotations.head: Annotation
- 根据注解列表的head获得语法树
annotation.tree: Tree
- 使用typeOf()方法,获取Type类型的类信息。
- 获取方法/成员字段的注解:
- 使用typeOf()方法,获取Type类型的类信息。
typeOf[Test]: Type
- 通过Type.decls/decl()方法筛选出目标成员的Symbol。
type.decl(TermName("ff ")): Symbol
- 通过Symbol.annotations获取List[Annotation](注解列表)。
symbol.annotations.head: Annotation
- 根据注解列表的head获得语法树
annotation.tree: Tree
- 使用typeOf()方法,获取Type类型的类信息。
- 获取方法参数的注解:
- 使用typeOf()方法,获取Type类型的类信息。
typeOf[Test]: Type
- 通过Type.decls/decl()方法筛选出目标方法的MethodSymbol。
type.decl(TermName("ff ")): MethodSymbol
通过MethodSymbol.paramLists方法获取目标方法的参数表(List[List[Symbol]]类型,方法柯里化可能会有多个参数表)。 - 通过Symbol.annotations获取List[Annotation](注解列表)。
methodSymbol.annotations.head: Annotation
- 使用typeOf()方法,获取Type类型的类信息。
解析注解Dome:
应使用Annotation.tree方法获取注解语法树,类型为scala.reflect.runtime.universe.Tree。
import scala.annotation.StaticAnnotation import scala.reflect.runtime.universe._ class CustomAnnotation(name: String, num: Int) extends StaticAnnotation { override def toString = s"Annotation args: name -> $name, num -> $num" } @CustomAnnotation("Annotation for Class", 2333) class Test { @CustomAnnotation("Annotation for Class", 6666) val ff = "" } object Main extends App { { // 获取类型注解 val tpe: Type = typeOf[Test] val symbol: Symbol = tpe.typeSymbol //获取类型符号信息 val annotation: Annotation = symbol.annotations.head val tree: Tree = annotation.tree //获取语法树 // 解析注解语法树... } { // 获取成员字段注解 val tpe: Type = typeOf[Test] val symbol: Symbol = tpe.decl(TermName("ff ")) //获取字段符号信息 val annotation: Annotation = symbol.annotations.head val tree: Tree = annotation.tree // 解析注解语法树... } }
1.3.3 Scala的implicit用法
平时我们比较常见的应该是使用implicit
修饰val
或者def
,如果在函数的定义中,有如下函数签名
def divide(x: Int, y: Int)(implicit i2d: Int => Double): Double
则调用上述的divide
方法时必须显示或隐式的传入一个类型为Int=>Double
的函数映射.如:
divide(1, 2)(i => i.toDouble)
如果在当前名称空间中,正好有了这个Int => Double
的函数映射,并且是隐式的,如:
implicit val test: Int => Double = i => i.toDouble
则函数调用的时候,则可以省略后面的隐式传入,编译器会自动使用当前名称空间的可用隐式,调用就变为:
divide(1, 2)
Implicit class
那么问题来了,如果implicit修饰的是class呢?
其实,我们可以认为带有这个关键字的类的主构造函数可用于隐式转换。
举个例子, 创建隐式类时,只需要在对应的类前加上implicit关键字。比如:
// 定义 object Helpers { implicit class IntWithTimes(x: Int) { def times[A](f: => A): Unit = { def loop(current: Int): Unit = if(current > 0) { f loop(current - 1) } loop(x) } } } // 调用 object main { import Helpers._ 5 times println("HI") }
可以发现, 5
是个Int型, 没有times方法,会自动查找名称空间,找到可以转换为IntWithTimes类型,并且带有times方法. 所以上面函数可以调用成功.
限制条件
- 使用隐式类时,类名必须在当前作用域内可见且无歧义,这一要求与隐式值等其他隐式类型转换方式类似。
- 只能在别的trait/类/对象内部定义。
- 构造函数只能携带一个非隐式参数。
- 在同一作用域内,不能有任何方法、成员或对象与隐式类同名。(也就说明case class 不可被implicit修饰,因为自动生成伴生类)
Implicit object
那么implicit修饰object又是什么鬼呢?
下面是个非常常见的例子:
object Math { trait NumberLike[T] { def plus(x: T, y: T): T def divide(x: T, y: Int): T def minus(x: T, y: T): T } object NumberLike { implicit object NumberLikeDouble extends NumberLike[Double] { def plus(x: Double, y: Double): Double = x + y def divide(x: Double, y: Int): Double = x / y def minus(x: Double, y: Double): Double = x - y } implicit object NumberLikeInt extends NumberLike[Int] { def plus(x: Int, y: Int): Int = x + y def divide(x: Int, y: Int): Int = x / y def minus(x: Int, y: Int): Int = x - y } } }
实际上,看到的隐式object我们可以认为它就是
implicit val NumberLikeDouble: NumberLike[Double] = new NumberLike[Double] { ... }