def handle_command(self, request: HandleCommandRequest): ''' general command handler that matches on command type url and runs appropriate handler method ''' # unpack current state prior_state = None prior_state_any: Any = get_field(request, "prior_state") validate(prior_state_any is not None, "missing prior state", error_code=StatusCode.INTERNAL)(self.context) if not prior_state_any.type_url.endswith("google.protobuf.Empty"): prior_state = unpack_any(prior_state_any, BankAccount) # get the handler by type url handler_key = request.command.type_url.split('.')[-1] logger.info(f"handling command {handler_key}") handler = self.command_router.get(handler_key) if not handler: raise Exception(f"unknown type {request.command.type_url}") return handler(request, prior_state, request.prior_event_meta)
def _cos_unpack_state(any_state): '''return state or None if a proto Empty message''' assert isinstance(any_state, Any), 'expecting Any' if any_state.type_url.endswith('google.protobuf.Empty'): logger.debug('converting google.protobuf.Empty to None') return None else: return unpack_any(any_state, BankAccount)
def _debit_account(self, request: HandleCommandRequest, prior_state: BankAccount, prior_event_meta: MetaData): '''handle debit''' validate(prior_event_meta.revision_number > 0, "account not found", StatusCode.NOT_FOUND)(self.context) command: DebitAccountRequest = unpack_any(request.command, DebitAccountRequest) validate(prior_state.account_balance - command.amount >= 0, "insufficient funds")(self.context) event = AccountDebited(account_id=prior_state.account_id, amount=command.amount) return HandleCommandResponse(event=pack_any(event))
def _credit_account(self, request: HandleCommandRequest, prior_state: BankAccount, prior_event_meta: MetaData): '''handle credit''' validate(prior_event_meta.revision_number > 0, "account not found", StatusCode.NOT_FOUND)(self.context) command: CreditAccountRequest = unpack_any(request.command, CreditAccountRequest) validate(command.amount >= 0, "credit must be positive")(self.context) # if crediting $0, return a no-op if command.amount == 0: return HandleCommandResponse() event = AccountCredited(account_id=prior_state.account_id, amount=command.amount) return HandleCommandResponse(event=pack_any(event))
def _open_account(self, request: HandleCommandRequest, prior_state: BankAccount, prior_event_meta: MetaData): '''handle open account command''' # the first event results in revision 1, so it can expect revision 0 prior validate(prior_event_meta.revision_number == 0, "account already exists", StatusCode.ALREADY_EXISTS)(self.context) command: OpenAccountRequest = unpack_any(request.command, OpenAccountRequest) validate(command.account_owner, "missing account owner", field_name="account_owner")(self.context) event = AccountOpened(account_id=prior_event_meta.entity_id, balance=command.balance, account_owner=command.account_owner) return HandleCommandResponse(event=pack_any(event))
def HandleReadSide(self, request, context): state = unpack_any(request.state, BankAccount) logger.info(f"readSideId={request.read_side_id}, entity={request.meta.entity_id}, revision={request.meta.revision_number}, balance={state.account_balance}, eventType={request.event.type_url}") return HandleReadSideResponse(successful=True)