blueeyes russian
TRANSCRIPT
BlueEyes
Michael Lagutko @mlagutko
Цели
• Создание RESTful сервисов • Высокая производительность (асинхронная обработка запросов)
• Масштабируемость (отсутствие поддержки состояния)
• Автомотическое тестирование (Независимость от сервера)
Service
• BlueEyesServiceBuilder • Комбинаторы оброботчиков запросов • Уникальное имя и версия • Жизненный цикл
• Контекст
Жизненный цикл сервиса
• Старт • Обработка запросов • Остановка
Комбинаторы оброботчиков запросов
• path(*padern*) { ... } • contentType(*mimeType*) { ... } • get { ... } • put { ... } • post { ... } • delete { ... } • … path("/emails"){ get { request => val response = H"pResponse(content = Some(emailIds)) Future.sync{response } } }
Комбинаторы путей
• Строковый: "/foo/bar” • Символьный: "/foo/'fooId” • Но основе регулярных выражений:"/foo/bar/baz.(?
<extension>\w{3,4})” path("/emails"){ path('emailId) { path("(?<bar>[a‐z]+)"){ get { request => val fooId = request.parameters('fooId) val extension = request.parameters('extension) ... } } } }
Комбинации оброботчиков запросов
produces(applicavon/json) { path("/users/") { get { request => // get list of all users ... } ~ path('userId) { parameter('userId) { userId => get { request => // get user ... } ~ put { request => // update user ... } } } } }
Контекст
• Конфигурация • Имя сервиса • Версия сервиса
Простой сервис trait EmailServices extends BlueEyesServiceBuilder { val emailService = service("email", "1.32") { context => startup { loadContactList(context.config("contactFile")) } ‐> request { contactList => path("/emails/") { produce(applicavon/json) { get { request => Future.async{ … H"pResponse(content = Some(JArray(emailIds))) } } } } ~ path('emailId) { produce(applicavon/json) { get { request => val emailId = request.parameters('emailId) ... Future.sync(H"pResponse(content = Some(emailObj))) } } } } ‐> shutdown { contactList => contactList.finalize } } }
HdpRequest
• method: HdpMethod • uri: URI • parameters: Map[Symbol, String]
• headers: HdpHeaders • content: Op]on[ByteChunk] • …
HdpResponse
• status: HdpStatus • headers: HdpHeaders • content: Op]on[ByteChunk] • …
ByteChunk
trait Chunk[T]{ def data: T
def next: Opvon[Future[Chunk[T]]]
}
type ByteChunk = Chunk[Array[Byte]]
Получение всего контента запроса
path("/emails/") { aggregate(None){ get { request: H"pRequest[ByteChunk] => Future.async{ … HdpResponse(content = Some(JArray(emailIds))) } } } }
Бизнесс данные
• Explicitly: post { request: H"pRequest[ByteChunk] => val bijecvon = BijecvonsChunkJson.ChunkToJValue val emailId = request.content.map(bijec]on(_)) ... Future.sync(HdpResponse(content = Some(bijec]on.Inverse(emailObj)))) }
• Implicitly: trait EmailServices extends BlueEyesServiceBuilder with Bijec]onsChunkJson … contentType(MimeTypes.applica]on/MimeTypes.json) { post { request: H"pRequest[JValue] => val emailId = request.content ... Future.sync(HdpResponse(content = Some(emailObj))) } }
Расширение сирвисов
• Фабрики сервис дескрипторов • Logging • Health monitors
• Request logging
Logging trait LogDemo extends BlueEyesServiceBuilder { val logDemoService = service("logdemo", "1.32") { logging { log => context => startup { request { state => path("/foo") { contentType(applicavon/json) { get { request => log.info("request at /foo") ... } } } } } }
Request Logging (W3C Extended Log format)
trait RequestLogDemo extends BlueEyesServiceBuilder { val requestLogDemoService = service("requestlogdemo", "1.32") { requestLogging{ context => startup { request { state => path("/foo") { contentType(applicavon/json) { get { request => ... } } } } } }
“requestLog” секция: "enabled = true | false" ( default = true ) "fields = see W3C Extended Log format" "roll = "never" | "hourly" | "daily" | "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" |
"saturday" " ( default = "never") "file = path to log file" "writeDelaySeconds = delay between flush to file" ( default = 1 )
Health Monitor
• /blueeyes/services/[serviceName]/v[serviceMajorVersion]/health
trait HealthMonitorDemo extends BlueEyesServiceBuilder { val healthMonitorService = service("healthmon", "1.32") { healthMonitor { monitor => context => request { state => path("/foo") { contentType(applicavon/json) { get { request => monitor.vme(".requests.foo.vming") { ... } } } } } } }
HdpClient
• HdpClient • HdpClientXLightWebEngines val responseFuture = client.get("hdp://myservice.com/foo")
responseFuture map {response => response.content.get}
def myService: HdpClient[JValue] = client.path("hdp://myservice.com/").contentType[JValue](applicavon/json)
val responseFuture = myService.get("api/v1")
responseFuture map {response => response.content.get}
Тестирваоние
• Полная потдержка Scala Specs • Не требует старта сервера
Пример простого теста
class EmailServicesSpec extends BlueEyesServiceSpecifica]on with EmailServices {
"EmailService" should {
"get emails" in {
val f = service.contentType[JValue](applicavon/json).get("/emails") f.value must eventually(beSomething)
val response = f.value.get
response.status mustEqual(HdpStatus(OK))
} }
}
Запуск сервиса
object AppServer extends BlueEyesServer with EmailServices with OrderProcessingServices with LoginServices with CatalogServices
val startFuture = AppServer .start
val stopFuture = AppServer .stop
java ‐jar appserver.jar ‐‐configFile /etc/default/appserver.conf
Данные
• Библиотека JSON
MongoDB
• BlueEyes MongoDB QL • Потдержка Mock MongoDB