State Machine Design Pattern

A design pattern with a few conceptual aspects:

  • The total number of states a system can be in are finite, and enumerable.
  • State transitions are limited to predefined rules.
  • The state of a system can be determined by an object’s properties.

Consider the following state machine concept in Kotlin: a payment processing system.

First, let’s define some statuses for the state machine:

1
2
3
4
5
6
7
8
enum Status {
    Requested,
    Processing,
    Approved,
    Declined,
    Error,
    Completed,
}

Second, we define a payment object:

1
2
3
4
5
6
7
class PaymentRequest(private val card: Card) {
    private var status: Status = Status.Requested
    
    fun getStatus(): Status {
        return status
    }
}

Third, we define an interface for different states:

1
2
3
interface PaymentProcessingState {
    fun process(paymentRequest: PaymentRequest): Status
}

And implementations for each state:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class RequestedPaymentProcessingState(private val userService: UserService) : PaymentProcessingState {
    fun process(paymentRequest: PaymentRequest): Status {
        if (!userService.userLoggedIn(paymentRequest)) {
            return Status.Error
        }
        // do some more logic
        ..
    }
}

class ProcessingPaymentProcessingState(private val paymentGateway: PaymentGateway) : PaymentProcessingState {
    fun process(paymentRequest: PaymentRequest): Status {
        try  {
            paymentGateway.charge(paymentRequest)
            return Status.Approved
        } catch (e: ChargeFailureException) {
            return Status.Declined
        }
    }
}

class ApprovedPaymentProcessingState : PaymentProcessingState {
    fun process(paymentRequest: PaymentRequest): Status {
        // mark a database record as approved
        ..
        return Status.Completed
    }
}

class DeclinedPaymentProcessingState : PaymentProcessingState {
    fun process(paymentRequest: PaymentRequest): Status {
        // mark a database record as declined
        ..
        // evaluate retry logic
        ..
        return Status.Completed
    }
}

class ErrorPaymentProcessingState : PaymentProcessingState {
    fun process(paymentRequest: PaymentRequest): Status {
        // mark a database record as error
        ..
        return Status.Completed
    }
}

class CompletedPaymentProcessingState : PaymentProcessingState {
    fun process(paymentRequest: PaymentRequest): Status {
        // log some analytics
        ..
        return Status.Completed
    }
}

Finally, we can define a payment processing service, which ties all the states together:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class PaymentProcessingService(
    private val userService: UserService, 
    private val paymentGateway: PaymentGateway,
)  {
    fun processPayment(paymentRequest: PaymentRequest) {
        when (paymentRequest.getStatus()) {
            Status.Requested -> RequestedPaymentProcessingState(userService).process(paymentRequest)
            Status.Processing -> ProcessingPaymentProcessingState(paymentGateway).process(paymentRequest)
            Status.Approved -> ApprovedPaymentProcessingState().process(paymentRequest)
            Status.Declined -> DeclinedPaymentProcessingState().process(paymentRequest)
            Status.Error -> ErrorPaymentProcessingState().process(paymentRequest)
            Status.Completed -> CompletedProcessingState().process(paymentRequest)
        }
    }
}

Further Reading