예제 #1
0
    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)
예제 #2
0
    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)
예제 #3
0
    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))
예제 #4
0
    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)
예제 #5
0
    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))
예제 #6
0
    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))
예제 #7
0
    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)