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 CreditAccount(self, request: CreditAccountRequest, context) -> ApiResponse: '''handle credit account''' logger.info("crediting account") active_global_tracer_span(context.get_active_span()) validate(request.amount >= 0, "credits must be positive")(context) result = self._cos_process_command(id=request.account_id, command=request, context=context) validate(result is not None, "state was none", StatusCode.INTERNAL)(context) return ApiResponse(account=result)
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 OpenAccount(self, request: OpenAccountRequest, context) -> ApiResponse: '''handle requests to open an account''' logger.info("opening account") active_global_tracer_span(context.get_active_span()) validate(request.balance >= 200, "minimum balance of 200 required")(context) account_id = request.account_id if not account_id: account_id = str(uuid4()) logger.info(f"opening account {account_id}") result = self._cos_process_command(id=account_id, command=request, context=context) validate(result is not None, "state was none", StatusCode.INTERNAL)(context) return ApiResponse(account=result)
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 Get(self, request: GetAccountRequest, context: grpc.ServicerContext) -> ApiResponse: '''handle get request''' logger.info("getting account") active_global_tracer_span(context.get_active_span()) client = self._get_cos_client() command = GetStateRequest() command.entity_id=request.account_id try: result = client.GetState(command) validate(result.HasField("state"), "state was none", StatusCode.NOT_FOUND)(context) state = self._cos_unpack_state(result.state) validate(state is not None, "state was none", StatusCode.NOT_FOUND)(context) except grpc.RpcError as e: if e.code() == StatusCode.NOT_FOUND: context.abort(code=e.code(), details=e.details()) else: context.abort(code=StatusCode.INTERNAL, details=e.details()) return ApiResponse(account=state)