欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Scala微服务架构 一

程序员文章站 2022-04-28 09:10:52
Scala微服务架构 一 ========= 因为公司的一个项目需求变动会非常频繁,同时改动在可控范围内,加上产品同学喜欢一些超前思维,我们的CTO决定提前开启八门,使用微服务架构。 那么什么是微服务架构呢? 1.1 微服务架构 “微服务”架构是近期软件应用领域非常热门的概念。 1.1.1 什么是微 ......

Scala微服务架构 一

因为公司的一个项目需求变动会非常频繁,同时改动在可控范围内,加上产品同学喜欢一些超前思维,我们的CTO决定提前开启八门,使用微服务架构。

那么什么是微服务架构呢?

1.1 微服务架构

“微服务”架构是近期软件应用领域非常热门的概念。

1.1.1 什么是微服务架构

微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。

特点

也就是说,微服务就是SOA的变种,在保证SOA业务分离能力的同时,减少每个业务粒度,同时去除了SOA中比较恼人的ESB.

*组装

这个图片从资源利用率的角度,体现了微服务一个非常给力的优点
Scala微服务架构 一

数据分离

同时,对应用组件封装的方式是整体架构与微服务架构的主要差异,微服务架构将相关联的业务逻辑及数据放在一起形成独立的边界,其目的是能在不影响其他应用组件(微服务)的情况下更快地交付并推出市场。
Scala微服务架构 一

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包路径下。

获取注解

  • 获取类的注解:
    1. 使用typeOf()方法,获取Type类型的类信息。typeOf[Test]: Type
    2. 通过Type.typeSymbol获取Symbol。type.typeSymbol: Symbol
    3. 通过Symbol.annotations获取List[Annotation](注解列表)。symbol.annotations.head: Annotation
    4. 根据注解列表的head获得语法树annotation.tree: Tree
  • 获取方法/成员字段的注解:
    1. 使用typeOf()方法,获取Type类型的类信息。typeOf[Test]: Type
    2. 通过Type.decls/decl()方法筛选出目标成员的Symbol。type.decl(TermName("ff ")): Symbol
    3. 通过Symbol.annotations获取List[Annotation](注解列表)。symbol.annotations.head: Annotation
    4. 根据注解列表的head获得语法树annotation.tree: Tree
  • 获取方法参数的注解:
    1. 使用typeOf()方法,获取Type类型的类信息。typeOf[Test]: Type
    2. 通过Type.decls/decl()方法筛选出目标方法的MethodSymbol。type.decl(TermName("ff ")): MethodSymbol
      通过MethodSymbol.paramLists方法获取目标方法的参数表(List[List[Symbol]]类型,方法柯里化可能会有多个参数表)。
    3. 通过Symbol.annotations获取List[Annotation](注解列表)。methodSymbol.annotations.head: Annotation

解析注解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方法. 所以上面函数可以调用成功.

限制条件

  1. 使用隐式类时,类名必须在当前作用域内可见且无歧义,这一要求与隐式值等其他隐式类型转换方式类似。
  2. 只能在别的trait/类/对象内部定义。
  3. 构造函数只能携带一个非隐式参数。
  4. 在同一作用域内,不能有任何方法、成员或对象与隐式类同名。(也就说明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] {
    ...
}