Scala微服務架構 一
Scala微服務架構 一
因為公司的一個項目需求變動會非常頻繁,同時改動在可控範圍內,加上產品同學喜歡一些超前思維,我們的CTO決定提前開啟八門,使用微服務架構。
那麽什麽是微服務架構呢?
1.1 微服務架構
“微服務”架構是近期軟件應用領域非常熱門的概念。
1.1.1 什麽是微服務架構
微服務是一種架構風格,一個大型復雜軟件應用由一個或多個微服務組成。系統中的各個微服務可被獨立部署,各個微服務之間是松耦合的。每個微服務僅關註於完成一件任務並很好地完成該任務。在所有情況下,每個任務代表著一個小的業務能力。
特點
也就是說,微服務就是SOA的變種,在保證SOA業務分離能力的同時,減少每個業務粒度,同時去除了SOA中比較惱人的ESB.
自由組裝
這個圖片從資源利用率的角度,體現了微服務一個非常給力的優點
數據分離
同時,對應用組件封裝的方式是整體架構與微服務架構的主要差異,微服務架構將相關聯的業務邏輯及數據放在一起形成獨立的邊界,其目的是能在不影響其他應用組件(微服務)的情況下更快地交付並推出市場。
1.2 前後對接協議 JsonApi
官網
1.3 本期語法糖
1.3.1 Scala的Package Object
官網講解https://www.scala-lang.org/docu/files/packageobjects/packageobjects.html
為啥使用Package Object
而不是Object
呢?
原因很簡單,答案是==代碼更幹凈==。如何做到的呢?
// 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] {
...
}
Scala微服務架構 一