Skip to content

Commit 08be370

Browse files
author
Zen Yui
authored
lagom-pb 0.9.0+3, simplify command handler response (#92)
* update lagom-pb 0.9.0+3 * update-protos and HandleCommandResponse * remove empty ReadSideHandlerSpec for now * override akka deps to make test pass * fix coverage problems
1 parent f9facc0 commit 08be370

File tree

13 files changed

+134
-742
lines changed

13 files changed

+134
-742
lines changed

code/service/src/main/scala/com/namely/chiefofstate/Aggregate.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ class Aggregate(
2222
eventHandler: EventHandler,
2323
encryptionAdapter: EncryptionAdapter
2424
) extends AggregateRoot(actorSystem, commandHandler, eventHandler, Empty.defaultInstance, encryptionAdapter) {
25-
// $COVERAGE-OFF$
25+
2626
override def aggregateName: String = "chiefOfState"
2727

28-
// $COVERAGE-ON$
2928
}

code/service/src/main/scala/com/namely/chiefofstate/AggregateCommandHandler.scala

Lines changed: 30 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import com.namely.protobuf.chiefofstate.v1.writeside.{
1111
HandleCommandResponse,
1212
WriteSideHandlerServiceClient
1313
}
14-
import com.namely.protobuf.chiefofstate.v1.writeside.HandleCommandResponse.ResponseType.{PersistAndReply, Reply}
1514
import com.namely.chiefofstate.config.HandlerSetting
1615
import io.grpc.{Status, StatusRuntimeException}
1716
import io.superflat.lagompb.{CommandHandler, ProtosRegistry}
@@ -98,16 +97,10 @@ class AggregateCommandHandler(
9897
if (priorEventMeta.revisionNumber > 0) {
9998
log.debug(s"[ChiefOfState] found state for entity ${command.entityId}")
10099
CommandHandlerResponse()
101-
.withSuccessResponse(
102-
SuccessCommandHandlerResponse()
103-
.withNoEvent(com.google.protobuf.empty.Empty.defaultInstance)
104-
)
105100
} else {
106101
log.error(s"[ChiefOfState] could not find state for entity ${command.entityId}")
107102
CommandHandlerResponse()
108-
.withFailedResponse(
109-
AggregateCommandHandler.GET_STATE_NOT_FOUND_FAILURE
110-
)
103+
.withFailure(FailureResponse().withNotFound("entity not found"))
111104
}
112105
}
113106

@@ -128,9 +121,10 @@ class AggregateCommandHandler(
128121
// make blocking gRPC call to handler service
129122
val responseAttempt: Try[HandleCommandResponse] = Try {
130123
// construct the request message
131-
val handleCommandRequest = HandleCommandRequest(command=remoteCommand.command)
132-
.withCurrentState(priorState)
133-
.withMeta(Util.toCosMetaData(priorEventMeta))
124+
val handleCommandRequest =
125+
HandleCommandRequest(command=remoteCommand.command)
126+
.withCurrentState(priorState)
127+
.withMeta(Util.toCosMetaData(priorEventMeta))
134128

135129
// create an akka gRPC request builder
136130
val futureResponse: Future[HandleCommandResponse] =
@@ -144,7 +138,7 @@ class AggregateCommandHandler(
144138
request.addHeader(header.key, value)
145139
case RemoteCommand.Header.Value.BytesValue(value) =>
146140
request.addHeader(header.key, akka.util.ByteString(value.toByteArray))
147-
case unhandled => throw new Exception(s"unhandled gRPC header type, ${unhandled.getClass.getName}")
141+
case _ => throw new Exception(s"header value must be string or bytes")
148142
}
149143
}
150144
)
@@ -168,55 +162,29 @@ class AggregateCommandHandler(
168162
* @return an instance of CommandHandlerResponse
169163
*/
170164
def handleRemoteResponseSuccess(response: HandleCommandResponse): CommandHandlerResponse = {
171-
response.responseType match {
172-
case PersistAndReply(persistAndReply) =>
165+
response.event match {
166+
case Some(event) =>
167+
173168
log.debug("[ChiefOfState] command handler return successfully. An event will be persisted...")
174-
val eventFQN: String = Util.getProtoFullyQualifiedName(persistAndReply.getEvent)
175169

176-
log.debug(s"[ChiefOfState] command handler event to persist $eventFQN")
177-
if (handlerSetting.enableProtoValidations) {
178-
if (handlerSetting.eventFQNs.contains(eventFQN)) {
179-
log.debug(s"[ChiefOfState] command handler event to persist $eventFQN is valid.")
180-
CommandHandlerResponse()
181-
.withSuccessResponse(
182-
SuccessCommandHandlerResponse()
183-
.withEvent(persistAndReply.getEvent)
184-
)
185-
} else {
186-
log.error(
187-
s"[ChiefOfState] command handler event to persist $eventFQN is not configured. Failing request"
188-
)
189-
CommandHandlerResponse()
190-
.withFailedResponse(
191-
FailedCommandHandlerResponse()
192-
.withReason(s"received unknown event type $eventFQN")
193-
.withCause(FailureCause.VALIDATION_ERROR)
194-
)
195-
}
196-
} else {
197-
log.debug(s"[ChiefOfState] command handler event to persist $eventFQN. FQN validation skipped.")
170+
val eventFQN: String = Util.getProtoFullyQualifiedName(event)
171+
172+
if (handlerSetting.enableProtoValidations && !handlerSetting.eventFQNs.contains(eventFQN)) {
173+
log.error(s"[ChiefOfState] command handler returned unknown event type, $eventFQN")
198174
CommandHandlerResponse()
199-
.withSuccessResponse(
200-
SuccessCommandHandlerResponse()
201-
.withEvent(persistAndReply.getEvent)
175+
.withFailure(
176+
FailureResponse()
177+
.withValidation(s"received unknown event type $eventFQN")
202178
)
179+
} else {
180+
log.debug(s"[ChiefOfState] command handler event to persist $eventFQN is valid.")
181+
CommandHandlerResponse()
182+
.withEvent(event)
203183
}
204184

205-
case Reply(_) =>
185+
case None =>
206186
log.debug("[ChiefOfState] command handler return successfully. No event will be persisted...")
207187
CommandHandlerResponse()
208-
.withSuccessResponse(
209-
SuccessCommandHandlerResponse()
210-
.withNoEvent(com.google.protobuf.empty.Empty.defaultInstance)
211-
)
212-
213-
case unhandled =>
214-
CommandHandlerResponse()
215-
.withFailedResponse(
216-
FailedCommandHandlerResponse()
217-
.withReason(s"command handler returned malformed event, ${unhandled.getClass.getName}")
218-
.withCause(FailureCause.INTERNAL_ERROR)
219-
)
220188
}
221189
}
222190

@@ -235,38 +203,28 @@ class AggregateCommandHandler(
235203
log.error(s"[ChiefOfState] $reason")
236204

237205
// handle specific gRPC error statuses
238-
val cause =
206+
val failureResponse =
239207
if (GRPC_FAILED_VALIDATION_STATUSES.contains(status.getCode)) {
240-
FailureCause.VALIDATION_ERROR
208+
FailureResponse().withValidation(status.getDescription)
241209
} else {
242-
FailureCause.INTERNAL_ERROR
210+
FailureResponse().withCritical(status.getDescription)
243211
}
244212

245-
CommandHandlerResponse()
246-
.withFailedResponse(
247-
FailedCommandHandlerResponse()
248-
.withReason(reason)
249-
.withCause(cause)
250-
)
213+
CommandHandlerResponse().withFailure(failureResponse)
251214

252215
case e: GrpcServiceException =>
253216
log.error(s"[ChiefOfState] handler gRPC failed with ${e.status.toString}", e)
254217
CommandHandlerResponse()
255-
.withFailedResponse(
256-
FailedCommandHandlerResponse()
257-
.withReason(e.getStatus.toString)
258-
.withCause(FailureCause.INTERNAL_ERROR)
218+
.withFailure(
219+
FailureResponse().withCritical(e.getStatus.getDescription)
259220
)
260221

261222
case e: Throwable =>
262223
log.error(s"[ChiefOfState] gRPC handler critical failure", e)
263224
CommandHandlerResponse()
264-
.withFailedResponse(
265-
FailedCommandHandlerResponse()
266-
.withReason(
267-
s"Critical error occurred handling command, ${e.getMessage}"
268-
)
269-
.withCause(FailureCause.INTERNAL_ERROR)
225+
.withFailure(
226+
FailureResponse()
227+
.withCritical(s"Critical error occurred handling command, ${e.getMessage}")
270228
)
271229
}
272230
}
@@ -286,10 +244,4 @@ object AggregateCommandHandler {
286244
Status.Code.OUT_OF_RANGE,
287245
Status.Code.PERMISSION_DENIED
288246
)
289-
290-
// constant failure for entity not found
291-
val GET_STATE_NOT_FOUND_FAILURE: FailedCommandHandlerResponse =
292-
FailedCommandHandlerResponse()
293-
.withReason("entity not found")
294-
.withCause(FailureCause.INTERNAL_ERROR)
295247
}

code/service/src/main/scala/com/namely/chiefofstate/AggregateEventHandler.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import com.namely.protobuf.chiefofstate.v1.writeside.{
88
HandleEventResponse,
99
WriteSideHandlerServiceClient
1010
}
11-
import io.superflat.lagompb.{EventHandler, GlobalException}
11+
import io.superflat.lagompb.EventHandler
1212
import io.superflat.lagompb.protobuf.v1.core.MetaData
1313
import org.slf4j.{Logger, LoggerFactory}
1414
import scalapb.GeneratedMessage
@@ -44,13 +44,13 @@ class AggregateEventHandler(
4444
) match {
4545

4646
case Failure(e) =>
47-
throw new GlobalException(e.getMessage)
47+
throw new Exception(e.getMessage)
4848

4949
case Success(eventualEventResponse: Future[HandleEventResponse]) =>
5050
Try {
5151
Await.result(eventualEventResponse, Duration.Inf)
5252
} match {
53-
case Failure(exception) => throw new GlobalException(exception.getMessage)
53+
case Failure(exception) => throw new Exception(exception.getMessage)
5454
case Success(handleEventResponse: HandleEventResponse) =>
5555

5656
val stateFQN: String = Util.getProtoFullyQualifiedName(handleEventResponse.getResultingState)
@@ -59,7 +59,7 @@ class AggregateEventHandler(
5959
// if enabled, validate the state type url returned by event handler
6060
if (handlerSetting.enableProtoValidations && !handlerSetting.stateFQNs.contains(stateFQN)) {
6161
log.error(s"[ChiefOfState]: command handler state to persist $stateFQN is not configured. Failing request")
62-
throw new GlobalException(s"received unknown state $stateFQN")
62+
throw new Exception(s"received unknown state $stateFQN")
6363
}
6464

6565
// pass through state returned by event handler

code/service/src/main/scala/com/namely/chiefofstate/Application.scala

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import com.lightbend.lagom.internal.persistence.ReadSideConfig
2525
* @param context the application context
2626
*/
2727
abstract class Application(context: LagomApplicationContext) extends BaseApplication(context) {
28-
// $COVERAGE-OFF$
2928

3029
// reflect encryption from config
3130
override def protoEncryption: Option[ProtoEncryption] = EncryptionSetting(config).encryption
@@ -79,22 +78,17 @@ abstract class Application(context: LagomApplicationContext) extends BaseApplica
7978
chiefOfStateReadProcessor.init()
8079
}
8180
}
82-
// $COVERAGE-ON$
8381
}
8482

8583
/**
8684
* ApplicationLoader boostraps the application at runtime
8785
*/
8886
class ApplicationLoader extends LagomApplicationLoader {
89-
90-
// $COVERAGE-OFF$
9187
override def load(context: LagomApplicationContext): LagomApplication =
9288
new Application(context) with AkkaDiscoveryComponents
9389

9490
override def loadDevMode(context: LagomApplicationContext): LagomApplication =
9591
new Application(context) with LagomDevModeComponents
9692

9793
override def describeService: Option[Descriptor] = Some(readDescriptor[ChiefOfStateService])
98-
99-
// $COVERAGE-ON$
10094
}

code/service/src/main/scala/com/namely/chiefofstate/GrpcServiceImpl.scala

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import com.namely.chiefofstate.config.SendCommandSettings
1616

1717
import scala.concurrent.{ExecutionContext, Future}
1818
import scala.util.{Try, Success, Failure}
19-
import io.superflat.lagompb.GlobalException
2019
import io.superflat.lagompb.protobuf.v1.core.StateWrapper
2120

2221
class GrpcServiceImpl(sys: ActorSystem,
@@ -106,24 +105,11 @@ class GrpcServiceImpl(sys: ActorSystem,
106105
)
107106
} else {
108107
sendCommand(in.entityId, in, Map.empty[String, String])
109-
.transform({
110-
// transform success to a GetStateResponse
111-
case Success(stateWrapper: StateWrapper) =>
112-
Success(
113-
GetStateResponse(
114-
state = stateWrapper.state,
115-
meta = stateWrapper.meta.map(Util.toCosMetaData)
116-
)
117-
)
118-
119-
// handle not-found errors specifically
120-
case Failure(e) if e.getMessage == AggregateCommandHandler.GET_STATE_NOT_FOUND_FAILURE.reason =>
121-
Failure(new GrpcServiceException(status = Status.NOT_FOUND.withDescription("COS could not find entity")))
122-
123-
// pass through other failures
124-
case Failure(e) =>
125-
log.error(s"unhandled error in getState", e)
126-
Failure(e)
108+
.map((stateWrapper: StateWrapper) => {
109+
GetStateResponse(
110+
state = stateWrapper.state,
111+
meta = stateWrapper.meta.map(Util.toCosMetaData)
112+
)
127113
})
128114
}
129115
}

code/service/src/main/scala/com/namely/chiefofstate/ReadSideHandler.scala

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import com.namely.protobuf.chiefofstate.v1.readside.{
1010
ReadSideHandlerServiceClient
1111
}
1212
import com.namely.chiefofstate.config.{HandlerSetting, ReadSideSetting}
13-
import io.superflat.lagompb.{ConfigReader, GlobalException}
13+
import io.superflat.lagompb.ConfigReader
1414
import io.superflat.lagompb.encryption.EncryptionAdapter
1515
import com.namely.chiefofstate.Util
1616
import io.superflat.lagompb.readside.{ReadSideEvent, ReadSideProcessor}
@@ -36,13 +36,10 @@ class ReadSideHandler(
3636
handlerSetting: HandlerSetting
3737
)(implicit ec: ExecutionContext)
3838
extends ReadSideProcessor(encryptionAdapter)(ec, actorSystem.toTyped) {
39-
// $COVERAGE-OFF$
4039

4140
override def projectionName: String =
4241
s"${grpcReadSideConfig.processorId}-${ConfigReader.serviceName}-readside-projection"
4342

44-
// $COVERAGE-ON$
45-
4643
private val COS_EVENT_TAG_HEADER = "x-cos-event-tag"
4744
private val COS_ENTITY_ID_HEADER = "x-cos-entity-id"
4845

@@ -63,22 +60,22 @@ class ReadSideHandler(
6360
log.error(
6461
s"[ChiefOfState]: ${grpcReadSideConfig.processorId} - unable to retrieve command handler response due to ${exception.getMessage}"
6562
)
66-
DBIOAction.failed(throw new GlobalException(exception.getMessage))
63+
DBIOAction.failed(throw new Exception(exception.getMessage))
6764
case Success(eventualReadSideResponse: Future[HandleReadSideResponse]) =>
6865
Try {
6966
Await.result(eventualReadSideResponse, Duration.Inf)
7067
} match {
7168
case Failure(exception) =>
7269
DBIOAction.failed(
73-
throw new GlobalException(
70+
throw new Exception(
7471
s"[ChiefOfState]: ${grpcReadSideConfig.processorId} - ${exception.getMessage}"
7572
)
7673
)
7774
case Success(value) =>
7875
if (value.successful) DBIOAction.successful(Done)
7976
else
8077
DBIOAction.failed(
81-
throw new GlobalException(
78+
throw new Exception(
8279
s"[ChiefOfState]: ${grpcReadSideConfig.processorId} - unable to handle readSide"
8380
)
8481
)

0 commit comments

Comments
 (0)