Skip to content

Commit

Permalink
Issue 51: Make event/state proto registration optional (#63)
Browse files Browse the repository at this point in the history
* issue #51: skip proto validations

* issue #51: fix tests
  • Loading branch information
kevinsamoei authored Aug 11, 2020
1 parent cba819f commit f5c3a9c
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 46 deletions.
2 changes: 0 additions & 2 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ services:
COS_PORT: 9000
WRITE_SIDE_HANDLER_SERVICE_HOST: host.docker.internal
WRITE_SIDE_HANDLER_SERVICE_PORT: 50052
HANDLER_SERVICE_STATES_PROTOS: ""
HANDLER_SERVICE_EVENTS_PROTOS: ""
TRACE_HOST: tracer
TRACE_PORT: 14268
COS_SERVICE_NAME: "chiefofstate"
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Chief-Of-State heavily relies on the robustness of [lagom-pb](https://github.com
| COS_ENCRYPTION_CLASS | java class to use for encryption | io.superflat.lagompb.encryption.NoEncryption |
| WRITE_SIDE_HANDLER_SERVICE_HOST | address of the gRPC writeSide handler service | <none> |
| WRITE_SIDE_HANDLER_SERVICE_PORT | port for the gRPC writeSide handler service | <none> |
| HANDLER_SERVICE_ENABLE_PROTO_VALIDATION | enable validation of the handler service states and events proto message FQN. If not set to `true` the validation will be skipped. | false |
| HANDLER_SERVICE_STATES_PROTOS | handler service states proto message FQN (fully qualified typeUrl). Format: `packagename.messagename`. This will be a comma separated list of values | <none> |
| HANDLER_SERVICE_EVENTS_PROTOS | handler service events proto message FQN (fully qualified typeUrl). Format: `packagename.messagename`. This will be a comma separated list of values | <none> |
| COS_SERVICE_NAME | service name | chiefofstate |
Expand Down
10 changes: 8 additions & 2 deletions service/src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,18 @@ chief-of-state {

# define settings for the handler services
handlers-settings {
# Whether to allow validation of the state and events FQNs
# if set to true, validation is done, by default it is false.
enable-proto-validation = false
enable-proto-validation = ${?HANDLER_SERVICE_ENABLE_PROTO_VALIDATION}
# define the fully qualified type url of the state proto
# example: namely.org_units.OrgUnit
states-proto = ${HANDLER_SERVICE_STATES_PROTOS}
states-proto = ""
states-proto = ${?HANDLER_SERVICE_STATES_PROTOS}
# list if the fully qualified type url of the events handled
# example: "namely.org_units.OrgUnitTypeCreated", "namely.org_units.OrgUnitTypeUpdated"
events-protos = ${HANDLER_SERVICE_EVENTS_PROTOS}
events-protos = ""
events-protos = ${?HANDLER_SERVICE_EVENTS_PROTOS}
}

send-command {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,24 +156,32 @@ class AggregateCommandHandler(
val eventFQN: String = Util.getProtoFullyQualifiedName(persistAndReply.getEvent)

log.debug(s"[ChiefOfState] command handler event to persist $eventFQN")

if (handlerSetting.eventFQNs.contains(eventFQN)) {
log.debug(s"[ChiefOfState] command handler event to persist $eventFQN is valid.")
if (handlerSetting.enableProtoValidations) {
if (handlerSetting.eventFQNs.contains(eventFQN)) {
log.debug(s"[ChiefOfState] command handler event to persist $eventFQN is valid.")
CommandHandlerResponse()
.withSuccessResponse(
SuccessCommandHandlerResponse()
.withEvent(Any.pack(Event().withEvent(persistAndReply.getEvent)))
)
} else {
log.error(
s"[ChiefOfState] command handler event to persist $eventFQN is not configured. Failing request"
)
CommandHandlerResponse()
.withFailedResponse(
FailedCommandHandlerResponse()
.withReason(s"received unknown event type $eventFQN")
.withCause(FailureCause.ValidationError)
)
}
} else {
log.debug(s"[ChiefOfState] command handler event to persist $eventFQN. FQN validation skipped.")
CommandHandlerResponse()
.withSuccessResponse(
SuccessCommandHandlerResponse()
.withEvent(Any.pack(Event().withEvent(persistAndReply.getEvent)))
)
} else {
log.error(
s"[ChiefOfState] command handler event to persist $eventFQN is not configured. Failing request"
)
CommandHandlerResponse()
.withFailedResponse(
FailedCommandHandlerResponse()
.withReason(s"received unknown event type $eventFQN")
.withCause(FailureCause.ValidationError)
)
}

case Reply(_) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,21 @@ class AggregateEventHandler(

log.debug(s"[ChiefOfState]: event handler state $stateFQN")

if (handlerSetting.stateFQNs.contains(stateFQN)) {
if (handlerSetting.enableProtoValidations) {
if (handlerSetting.stateFQNs.contains(stateFQN)) {

log.debug(s"[ChiefOfState]: event handler state $stateFQN is valid.")
log.debug(s"[ChiefOfState]: event handler state $stateFQN is valid.")

priorState.update(_.currentState := handleEventResponse.getResultingState)
priorState.update(_.currentState := handleEventResponse.getResultingState)
} else {
throw new GlobalException(
s"[ChiefOfState]: command handler state to persist $stateFQN is not configured. Failing request"
)
}
} else {
throw new GlobalException(
s"[ChiefOfState]: command handler state to persist $stateFQN is not configured. Failing request"
)
log.debug(s"[ChiefOfState]: event handler state: $stateFQN. FQN validation skipped.")

priorState.update(_.currentState := handleEventResponse.getResultingState)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import com.typesafe.config.{Config, ConfigException}
* This class need to be kick started on boot. When the configuration variables are not set
* an exception should be thrown forcing the implementor to set the appropriate value
*/
case class HandlerSetting(stateFQNs: Seq[String], eventFQNs: Seq[String])
case class HandlerSetting(enableProtoValidations: Boolean, stateFQNs: Seq[String], eventFQNs: Seq[String])

object HandlerSetting {

Expand Down Expand Up @@ -36,9 +36,13 @@ object HandlerSetting {
.map(_.trim)
.filter(_.nonEmpty)

if (stateProtos.isEmpty || eventProtos.isEmpty)
throw new RuntimeException("[ChiefOfState] handler service settings not properly set.")
val enableProtoValidations = config.getBoolean("chief-of-state.handlers-settings.enable-proto-validation")

new HandlerSetting(stateProtos, eventProtos)
if (enableProtoValidations) {
if (stateProtos.isEmpty || eventProtos.isEmpty)
throw new RuntimeException("[ChiefOfState] handler service settings not properly set.")
}

new HandlerSetting(enableProtoValidations, stateProtos, eventProtos)
}
}
2 changes: 2 additions & 0 deletions service/src/test/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ akka {
chief-of-state {
# define settings for the handler services
handlers-settings {
# Whether to allow validation of the state and events FQNs
enable-proto-validation = false
# define the fully qualified type url of the state proto
# example: namely.org_units.OrgUnit
states-proto = ""
Expand Down
2 changes: 2 additions & 0 deletions service/src/test/resources/handler-settings.conf
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
chief-of-state {
# define settings for the handler services
handlers-settings {
# Whether to allow validation of the state and events FQNs
enable-proto-validation = true
# define the fully qualified type url of the state proto
# example: namely.org_units.OrgUnit
states-proto = ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class AggregateCommandHandlerSpec extends BaseSpec with MockFactory {
val testHandlerSetting: HandlerSetting = {
val stateProto: Seq[String] = Seq(Util.getProtoFullyQualifiedName(Any.pack(Account.defaultInstance)))
val eventsProtos: Seq[String] = Seq(Util.getProtoFullyQualifiedName(Any.pack(AccountOpened.defaultInstance)))
HandlerSetting(stateProto, eventsProtos)
HandlerSetting(enableProtoValidations = true, stateProto, eventsProtos)
}

"main commandHandler" should {
Expand Down Expand Up @@ -326,7 +326,7 @@ class AggregateCommandHandlerSpec extends BaseSpec with MockFactory {
)

// let us execute the request
val badHandlerSettings: HandlerSetting = HandlerSetting(Seq(), Seq())
val badHandlerSettings: HandlerSetting = HandlerSetting(enableProtoValidations = true, Seq(), Seq())
val cmdhandler = new AggregateCommandHandler(null, null, badHandlerSettings)
val result: CommandHandlerResponse = cmdhandler.handleRemoteResponseSuccess(badResponse)

Expand All @@ -341,6 +341,33 @@ class AggregateCommandHandlerSpec extends BaseSpec with MockFactory {

}

"handle command when event type in handler settings is disabled as expected" in {

val event = AccountOpened()
.withAccountNumber("123445")
.withAccountUuid(UUID.randomUUID.toString)

val response = HandleCommandResponse()
.withPersistAndReply(
PersistAndReply()
.withEvent(Any.pack(event))
)

// set enableProtoValidations to false and not provide event and state protos
val handlerSettings: HandlerSetting = HandlerSetting(enableProtoValidations = false, Seq(), Seq())
val cmdhandler = new AggregateCommandHandler(null, null, handlerSettings)
val result: CommandHandlerResponse = cmdhandler.handleRemoteResponseSuccess(response)

result shouldBe (
CommandHandlerResponse()
.withSuccessResponse(
SuccessCommandHandlerResponse()
.withEvent(Any.pack(Event().withEvent(Any.pack(event))))
)
)

}

"handle command successfully as expected with no event to persist" in {
// let us execute the request
val cmdhandler = new AggregateCommandHandler(null, null, testHandlerSetting)
Expand Down Expand Up @@ -456,7 +483,7 @@ class AggregateCommandHandlerSpec extends BaseSpec with MockFactory {
// create a CommandHandler with a mock client
val stateProto: Seq[String] = Seq(Util.getProtoFullyQualifiedName(Any.pack(Account.defaultInstance)))
val eventsProtos: Seq[String] = Seq(Util.getProtoFullyQualifiedName(Any.pack(AccountOpened.defaultInstance)))
val handlerSetting: HandlerSetting = HandlerSetting(stateProto, eventsProtos)
val handlerSetting: HandlerSetting = HandlerSetting(enableProtoValidations = true, stateProto, eventsProtos)
val mockGrpcClient = mock[WriteSideHandlerServiceClient]
val cmdhandler = new AggregateCommandHandler(null, mockGrpcClient, handlerSetting)

Expand All @@ -481,7 +508,7 @@ class AggregateCommandHandlerSpec extends BaseSpec with MockFactory {
// create a CommandHandler with a mock client
val stateProto: Seq[String] = Seq(Util.getProtoFullyQualifiedName(Any.pack(Account.defaultInstance)))
val eventsProtos: Seq[String] = Seq(Util.getProtoFullyQualifiedName(Any.pack(AccountOpened.defaultInstance)))
val handlerSetting: HandlerSetting = HandlerSetting(stateProto, eventsProtos)
val handlerSetting: HandlerSetting = HandlerSetting(enableProtoValidations = true, stateProto, eventsProtos)
val mockGrpcClient = mock[WriteSideHandlerServiceClient]
val cmdhandler = new AggregateCommandHandler(null, mockGrpcClient, handlerSetting)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ import com.google.protobuf.any.Any
import com.namely.protobuf.chief_of_state.common
import com.namely.protobuf.chief_of_state.persistence.{Event, State}
import com.namely.protobuf.chief_of_state.tests.{Account, AccountOpened}
import com.namely.protobuf.chief_of_state.writeside.{
HandleEventRequest,
HandleEventResponse,
WriteSideHandlerServiceClient
}
import com.namely.protobuf.chief_of_state.writeside.{HandleEventRequest, HandleEventResponse, WriteSideHandlerServiceClient}
import io.grpc.Status
import io.superflat.lagompb.GlobalException
import io.superflat.lagompb.protobuf.core.MetaData
Expand All @@ -34,7 +30,7 @@ class AggregateEventHandlerSpec extends BaseSpec with MockFactory {
val eventsProtos: Seq[String] =
Seq(Util.getProtoFullyQualifiedName(Any.pack(AccountOpened.defaultInstance)))

val handlerSetting: HandlerSetting = HandlerSetting(stateProto, eventsProtos)
val handlerSetting: HandlerSetting = HandlerSetting(enableProtoValidations = true, stateProto, eventsProtos)

val event = AccountOpened()
.withAccountNumber(accountNumber)
Expand Down Expand Up @@ -85,7 +81,54 @@ class AggregateEventHandlerSpec extends BaseSpec with MockFactory {
val eventsProtos: Seq[String] =
Seq(Util.getProtoFullyQualifiedName(Any.pack(AccountOpened.defaultInstance)))

val handlerSetting: HandlerSetting = HandlerSetting(stateProto, eventsProtos)
val handlerSetting: HandlerSetting = HandlerSetting(enableProtoValidations = true, stateProto, eventsProtos)

val event = AccountOpened()
.withAccountNumber(accountNumber)
.withAccountUuid(accouuntId)

val resultingState = Account()
.withAccountNumber(accountNumber)
.withAccountUuid(accouuntId)
.withBalance(100)

// let us create a mock instance of the handler service client
val mockGrpcClient = mock[WriteSideHandlerServiceClient]

(mockGrpcClient
.handleEvent(_: HandleEventRequest))
.expects(
HandleEventRequest()
.withEvent(Any.pack(event))
.withCurrentState(priorState.getCurrentState)
.withMeta(
common
.MetaData()
.withData(eventMeta.data)
.withRevisionDate(eventMeta.getRevisionDate)
.withRevisionNumber(eventMeta.revisionNumber)
)
)
.returning(
Future.successful(
HandleEventResponse()
.withResultingState(Any.pack(resultingState))
)
)

val eventHandler: AggregateEventHandler = new AggregateEventHandler(null, mockGrpcClient, handlerSetting)
a[GlobalException] shouldBe thrownBy(
eventHandler.handle(Event().withEvent(Any.pack(event)), priorState, eventMeta)
)
}

"handle event when event protos validation is disabled in handler settings as expected" in {
val priorState: State = State.defaultInstance
val eventMeta: MetaData = MetaData.defaultInstance
val accouuntId: String = UUID.randomUUID.toString
val accountNumber: String = "123445"

val handlerSetting: HandlerSetting = HandlerSetting(enableProtoValidations = false, Seq.empty, Seq.empty)

val event = AccountOpened()
.withAccountNumber(accountNumber)
Expand Down Expand Up @@ -121,6 +164,53 @@ class AggregateEventHandlerSpec extends BaseSpec with MockFactory {
)

val eventHandler: AggregateEventHandler = new AggregateEventHandler(null, mockGrpcClient, handlerSetting)

noException shouldBe thrownBy(eventHandler.handle(Event().withEvent(Any.pack(event)), priorState, eventMeta))
}

"handle event when event validation is enabled and the FQNs not provided in handler settings as expected" in {
val priorState: State = State.defaultInstance
val eventMeta: MetaData = MetaData.defaultInstance
val accouuntId: String = UUID.randomUUID.toString
val accountNumber: String = "123445"

val handlerSetting: HandlerSetting = HandlerSetting(enableProtoValidations = true, Seq.empty, Seq.empty)

val event = AccountOpened()
.withAccountNumber(accountNumber)
.withAccountUuid(accouuntId)

val resultingState = Account()
.withAccountNumber(accountNumber)
.withAccountUuid(accouuntId)
.withBalance(100)

// let us create a mock instance of the handler service client
val mockGrpcClient = mock[WriteSideHandlerServiceClient]

(mockGrpcClient
.handleEvent(_: HandleEventRequest))
.expects(
HandleEventRequest()
.withEvent(Any.pack(event))
.withCurrentState(priorState.getCurrentState)
.withMeta(
common
.MetaData()
.withData(eventMeta.data)
.withRevisionDate(eventMeta.getRevisionDate)
.withRevisionNumber(eventMeta.revisionNumber)
)
)
.returning(
Future.successful(
HandleEventResponse()
.withResultingState(Any.pack(resultingState))
)
)

val eventHandler: AggregateEventHandler = new AggregateEventHandler(null, mockGrpcClient, handlerSetting)

a[GlobalException] shouldBe thrownBy(
eventHandler.handle(Event().withEvent(Any.pack(event)), priorState, eventMeta)
)
Expand All @@ -136,7 +226,7 @@ class AggregateEventHandlerSpec extends BaseSpec with MockFactory {
val eventsProtos: Seq[String] =
Seq(Util.getProtoFullyQualifiedName(Any.pack(AccountOpened.defaultInstance)))

val handlerSetting: HandlerSetting = HandlerSetting(stateProto, eventsProtos)
val handlerSetting: HandlerSetting = HandlerSetting(enableProtoValidations = true, stateProto, eventsProtos)

val event = AccountOpened()
.withAccountNumber(accountNumber)
Expand Down Expand Up @@ -177,7 +267,7 @@ class AggregateEventHandlerSpec extends BaseSpec with MockFactory {
val eventsProtos: Seq[String] =
Seq(Util.getProtoFullyQualifiedName(Any.pack(AccountOpened.defaultInstance)))

val handlerSetting: HandlerSetting = HandlerSetting(stateProto, eventsProtos)
val handlerSetting: HandlerSetting = HandlerSetting(enableProtoValidations = true, stateProto, eventsProtos)

val event = AccountOpened()
.withAccountNumber(accountNumber)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ class HandlerSettingSpec extends BaseSpec {
an[RuntimeException] shouldBe thrownBy(HandlerSetting(config))
}

"success to load settings when enable-validation is disabled" in {
val config: Config = ConfigFactory
.parseResources("handler-settings.conf")
.withValue(
"chief-of-state.handlers-settings.enable-proto-validation",
ConfigValueFactory.fromAnyRef(false)
)
.resolve()

noException shouldBe thrownBy(HandlerSetting(config))
}

"fail to load settings because env variables for events-protos not set" in {
val config: Config = ConfigFactory
.parseResources("handler-settings.conf")
Expand Down
Loading

0 comments on commit f5c3a9c

Please sign in to comment.