-
Notifications
You must be signed in to change notification settings - Fork 15
/
SprayJsonFormatBenchmark.scala
284 lines (251 loc) · 12.2 KB
/
SprayJsonFormatBenchmark.scala
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
package pl.iterators.kebs_benchmarks
import java.time.format.DateTimeFormatter
import java.time.{LocalDate, LocalTime}
import java.util.concurrent.TimeUnit
import org.apache.pekko.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import org.apache.pekko.http.scaladsl.marshalling.ToResponseMarshallable
import org.apache.pekko.http.scaladsl.model.StatusCodes._
import org.apache.pekko.http.scaladsl.model.{ContentTypes, HttpEntity}
import org.apache.pekko.http.scaladsl.server.Directives._
import org.apache.pekko.http.scaladsl.testkit.ScalatestRouteTest
import org.openjdk.jmh.annotations._
import org.scalatest.{FunSpec, Matchers}
import pl.iterators.kebs.json.KebsSpray
import spray.json._
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try
case class Contact(phoneNumber: String, url: String)
case class LocationShort(city: String, latitude: BigDecimal, longitude: BigDecimal, neighborhood: String, timeZone: String)
case class LocationFull(city: String,
latitude: BigDecimal,
longitude: BigDecimal,
neighborhood: String,
timeZone: String,
address_1: String,
state: String,
postalCode: String)
case class Reservation(deepLink: String, id: Int, seatType: String, timeSlot: String, webLink: String)
case class TravelTime(distance: Double, driving: Int, walking: Int)
case class AvailableReservation(contact: Contact,
deepLink: String,
location: LocationShort,
images: Option[List[String]],
name: String,
priceRangeId: Int,
reservations: List[Reservation],
travelTime: TravelTime,
`type`: String,
webLink: String)
case class AvailableReservationsResponse(available: List[AvailableReservation])
case class Rater(name: String, score: Double, scale: Double, image: String)
case class Venue(location: LocationFull,
name: String,
priceRangeId: Int,
`type`: String,
images: List[String],
about: String,
tagline: String,
rater: Rater)
case class BookedReservation(deepLink: String, seatType: String, timeSlot: String)
case class PaymentDetails(fee: Option[BigDecimal], serviceCharge: Option[BigDecimal], tax: Option[BigDecimal], total: Option[BigDecimal])
case class Payment(details: Option[PaymentDetails])
case class ReservationDetailsResponse(venue: Venue, reservation: BookedReservation, payment: Option[Payment], token: String)
case class Reservations(reservations: List[RequestedReservation])
case class RequestedReservationDetails(day: LocalDate, timeSlot: LocalTime)
case class Fee(amount: BigDecimal)
case class Cancellation(fee: Option[Fee])
case class RequestedReservation(token: String, reservation: RequestedReservationDetails, cancellation: Option[Cancellation])
sealed trait DetailsResult
object DetailsResult {
case class Success(details: ReservationDetailsResponse) extends DetailsResult
case object Expired extends DetailsResult
case class Error(message: String) extends DetailsResult
}
trait Protocol extends DefaultJsonProtocol with SprayJsonSupport {
implicit val localTimeFormat = new JsonFormat[LocalTime] {
override def write(obj: LocalTime): JsValue = JsString(formatter.format(obj))
override def read(json: JsValue): LocalTime = {
json match {
case JsString(lTString) =>
Try(LocalTime.parse(lTString, formatter)).getOrElse(deserializationError(deserializationErrorMessage))
case _ => deserializationError(deserializationErrorMessage)
}
}
private val formatter = DateTimeFormatter.ISO_LOCAL_TIME
private val deserializationErrorMessage =
s"Expected date time in ISO offset date time format ex. ${LocalTime.now().format(formatter)}"
}
implicit val localDateFormat = new JsonFormat[LocalDate] {
override def write(obj: LocalDate): JsValue = JsString(formatter.format(obj))
override def read(json: JsValue): LocalDate = {
json match {
case JsString(lDString) =>
Try(LocalDate.parse(lDString, formatter)).getOrElse(deserializationError(deserializationErrorMessage))
case _ => deserializationError(deserializationErrorMessage)
}
}
private val formatter = DateTimeFormatter.ISO_LOCAL_DATE
private val deserializationErrorMessage =
s"Expected date time in ISO offset date time format ex. ${LocalDate.now().format(formatter)}"
}
}
abstract class Service {
def getAvailableReservations: Future[AvailableReservationsResponse]
def getReservationDetails(id: Int): Future[DetailsResult]
def getUserReservations(accessToken: String): Future[Reservations]
}
object BeforeKebs {
object Protocol extends Protocol {
def jsonFlatFormat[P, T <: Product](construct: P => T)(implicit jw: JsonWriter[P], jr: JsonReader[P]): JsonFormat[T] =
new JsonFormat[T] {
override def read(json: JsValue): T = construct(jr.read(json))
override def write(obj: T): JsValue = jw.write(obj.productElement(0).asInstanceOf[P])
}
implicit val contactFormat = jsonFormat2(Contact.apply)
implicit val locationShortFormat = jsonFormat5(LocationShort.apply)
implicit val locationFullFormat = jsonFormat8(LocationFull.apply)
implicit val reservationFormat = jsonFormat5(Reservation.apply)
implicit val travelTimeFormat = jsonFormat3(TravelTime.apply)
implicit val availableReservationFormat = jsonFormat10(AvailableReservation.apply)
implicit val availableReservationResponseFormat = jsonFormat1(AvailableReservationsResponse.apply)
implicit val raterFormat = jsonFormat4(Rater.apply)
implicit val venueResponseFormat = jsonFormat8(Venue.apply)
implicit val bookedReservationResponseFormat = jsonFormat3(BookedReservation.apply)
implicit val paymentDetailsFormat = jsonFormat4(PaymentDetails.apply)
implicit val paymentFormat = jsonFormat1(Payment.apply)
implicit val reservationDetailsResponseFormat = jsonFormat4(ReservationDetailsResponse.apply)
implicit val feeFormat = jsonFormat1(Fee.apply)
private implicit val cancellationFormat = jsonFormat1(Cancellation.apply)
implicit val requestedReservationDetailsFormat = jsonFormat2(RequestedReservationDetails.apply)
implicit val requestedReservationFormat = jsonFormat3(RequestedReservation.apply)
implicit val reservationsFormat = jsonFormat1(Reservations.apply)
}
class Router(service: Service)(implicit ec: ExecutionContext) {
import Protocol._
val getAvailableReservations = (get & pathEndOrSingleSlash) {
complete(service.getAvailableReservations)
}
val getReservationDetails = (get & path(IntNumber)) { id =>
complete {
service.getReservationDetails(id).map[ToResponseMarshallable] {
case DetailsResult.Success(res) => OK -> res
case DetailsResult.Expired => NotFound
case DetailsResult.Error(error) => BadRequest -> error
}
}
}
val getUserReservations = (get & parameters('token)) { token =>
complete(service.getUserReservations(token))
}
}
}
object AfterKebs {
object Protocol extends Protocol with KebsSpray
class Router(service: Service)(implicit ec: ExecutionContext) {
import Protocol._
val getAvailableReservations = (get & pathEndOrSingleSlash) {
complete(service.getAvailableReservations)
}
val getReservationDetails = (get & path(IntNumber)) { id =>
complete {
service.getReservationDetails(id).map[ToResponseMarshallable] {
case DetailsResult.Success(res) => OK -> res
case DetailsResult.Expired => NotFound
case DetailsResult.Error(error) => BadRequest -> error
}
}
}
val getUserReservations = (get & parameters('token)) { token =>
complete(service.getUserReservations(token))
}
}
}
object SprayJsonFormatBenchmark {
val fakeService = new Service {
val sampleAvailableReservationsResponse = AvailableReservationsResponse(
List(
AvailableReservation(
Contact("12 270 24 88", "<none>"),
"?",
LocationShort("Czernichów", 49.9915924, 19.6754663, "Czernichów", "CET"),
None,
"RAPIO",
1,
List(
Reservation("?", 1, "stolik", "20:00-21:00", "<none>"),
Reservation("?", 2, "stolik", "20:00-21:00", "<none>"),
Reservation("?", 3, "stolik", "20:00-21:00", "<none>")
),
TravelTime(distance = 100, driving = 99, walking = 1),
"pizzera",
"<none>"
)))
val sampleReservetionDetailsResponse = ReservationDetailsResponse(
Venue(
LocationFull("Czernichów", 49.9915924, 19.6754663, "Czernichów", "CET", "Czernichów 232", "małopolskie", "32-071"),
"RAPIO",
1,
"pizzeria",
List.empty,
"Pizzeria & Restauracja RAPIO",
"Pizzeria & Restauracja RAPIO",
Rater("?", 100.0, 1.0, "?")
),
BookedReservation("?", "stolik", "20.00-21:00"),
Some(Payment(Some(PaymentDetails(fee = None, serviceCharge = Some(100), tax = Some(0.08), total = Some(108))))),
token = "abcdefgh"
)
val sampleReservations = Reservations(List(
RequestedReservation("abcdefgh", RequestedReservationDetails(LocalDate.now(), LocalTime.now()), Some(Cancellation(Some(Fee(10)))))))
override def getReservationDetails(id: Int) = Future.successful(DetailsResult.Success(sampleReservetionDetailsResponse))
override def getUserReservations(accessToken: String) = Future.successful(sampleReservations)
override def getAvailableReservations = Future.successful(sampleAvailableReservationsResponse)
}
import ExecutionContext.Implicits.global
val beforeKebsRouter = new BeforeKebs.Router(fakeService)
val afterKebsRouter = new AfterKebs.Router(fakeService)
val beforeKebsRoutes = beforeKebsRouter.getUserReservations ~ beforeKebsRouter.getAvailableReservations ~ beforeKebsRouter.getReservationDetails
val afterKebsRoutes = afterKebsRouter.getUserReservations ~ afterKebsRouter.getAvailableReservations ~ afterKebsRouter.getReservationDetails
}
@State(Scope.Benchmark)
@Warmup(iterations = 10, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 100, timeUnit = TimeUnit.MILLISECONDS)
@Fork(1)
class SprayJsonFormatBenchmark extends FunSpec with Matchers with ScalatestRouteTest {
@Benchmark
@BenchmarkMode(Array(Mode.AverageTime))
@OutputTimeUnit(TimeUnit.MILLISECONDS)
def sprayJsonCostBeforeKebs1 = Get("/?token=token") ~> SprayJsonFormatBenchmark.beforeKebsRoutes ~> check {
status shouldEqual OK
}
@Benchmark
@BenchmarkMode(Array(Mode.AverageTime))
@OutputTimeUnit(TimeUnit.MILLISECONDS)
def sprayJsonCostBeforeKebs2 = Get("/") ~> SprayJsonFormatBenchmark.beforeKebsRoutes ~> check {
status shouldEqual OK
}
@Benchmark
@BenchmarkMode(Array(Mode.AverageTime))
@OutputTimeUnit(TimeUnit.MILLISECONDS)
def sprayJsonCostBeforeKebs3 = Get("/1") ~> SprayJsonFormatBenchmark.beforeKebsRoutes ~> check {
status shouldEqual OK
}
@Benchmark
@BenchmarkMode(Array(Mode.AverageTime))
@OutputTimeUnit(TimeUnit.MILLISECONDS)
def sprayJsonCostAfterKebs1 = Get("/?token=token") ~> SprayJsonFormatBenchmark.afterKebsRoutes ~> check {
status shouldEqual OK
}
@Benchmark
@BenchmarkMode(Array(Mode.AverageTime))
@OutputTimeUnit(TimeUnit.MILLISECONDS)
def sprayJsonCostAfterKebs2 = Get("/") ~> SprayJsonFormatBenchmark.afterKebsRoutes ~> check {
status shouldEqual OK
}
@Benchmark
@BenchmarkMode(Array(Mode.AverageTime))
@OutputTimeUnit(TimeUnit.MILLISECONDS)
def sprayJsonCostAfterKebs3 = Get("/1") ~> SprayJsonFormatBenchmark.afterKebsRoutes ~> check {
status shouldEqual OK
}
}