Design a turnstile with locked and unlocked states, and implement insert_token() and push() methods.

Hard
4 years ago

Let's design a turnstile. A turnstile is a type of gate which allows one person to pass at a time. Consider a turnstile at a subway station. There are two panels that rotate to allow a single person passage. The turnstile has two states, Locked and Unlocked, and the customer can either Push the turnstile or Insert a token/card. Here is the state transition table:

EventCurrent StateActionNext State
PushLockedLocked
TokenLockedUnlockUnlocked
PushUnlockedPass throughLocked
TokenUnlocked(discarded)Unlocked

Requirements:

  1. Model the state and behavior of a simple turnstile.
  2. Implement two methods:
    • insert_token()
    • push()
  3. Consider edge cases and error handling. What happens if someone tries to push without inserting a token? What happens if the system malfunctions?

Example:

turnstile = Turnstile()
print(turnstile.state)  # Output: Locked
turnstile.push()
print(turnstile.state)  # Output: Locked
turnstile.insert_token()
print(turnstile.state)  # Output: Unlocked
turnstile.push()
print(turnstile.state)  # Output: Locked

How would you approach this design and implementation, keeping in mind scalability, maintainability, and potential future extensions such as different types of tokens or remote unlocking?

Sample Answer

Let's walk through the design and implementation of a turnstile system, considering scalability, maintainability, and future extensions.

1. Requirements

  • Model the state and behavior of a simple turnstile.
  • Implement insert_token() and push() methods.
  • Handle edge cases and error conditions.
  • Consider future extensions (different token types, remote unlocking).

2. High-Level Design

The turnstile system can be modeled using a state machine. It has two primary states: Locked and Unlocked. The transitions between these states are triggered by two events: insert_token() and push().

3. Implementation

Here's a Python implementation of the turnstile:

class Turnstile:
    def __init__(self):
        self.state = "Locked"

    def insert_token(self):
        if self.state == "Locked":
            self.state = "Unlocked"
            print("Token accepted. Turnstile unlocked.")
        else:
            print("Token discarded. Turnstile already unlocked.")

    def push(self):
        if self.state == "Locked":
            print("Cannot pass. Turnstile is locked.")
        else:
            self.state = "Locked"
            print("Passed through. Turnstile locked.")

# Example usage
turnstile = Turnstile()
print(f"Initial state: {turnstile.state}")
turnstile.push()
print(f"State after push: {turnstile.state}")
turnstile.insert_token()
print(f"State after token: {turnstile.state}")
turnstile.push()
print(f"State after push: {turnstile.state}")

4. Data Model

For this simple implementation, we only need a single attribute: state. In a more complex system, we might need to track token types, transaction history, etc.

FieldTypeDescription
statestringCurrent state (Locked/Unlocked)

5. Endpoints

In a real-world scenario, the turnstile might be controlled via an API. Here's a simplified API design:

  • /insert_token
    • Method: POST
    • Request:
    {
      "token_type": "subway_card",
      "token_id": "1234567890"
    }
*   Response:
    {
      "status": "success",
      "message": "Turnstile unlocked"
    }
  • /push
    • Method: POST
    • Request: (Empty)
    • Response:
    {
      "status": "success",
      "message": "Passed through. Turnstile locked."
    }

6. Tradeoffs

AspectApproachProsCons
State MachineSimple string-based state representationEasy to understand, quick to implementNot scalable for complex state transitions, error-prone
APIREST API with JSON payloadsStandard, easy to integrate with other systemsAdds overhead, might not be necessary for simple implementations
Error HandlingBasic print statementsSimple for demonstrationNot robust, lacks logging, monitoring, and alerting

7. Other Approaches

  1. Finite State Machine Library:
    • Instead of manually managing the state transitions with if/else statements, a finite state machine library (e.g., transitions in Python) can be used. This provides a more structured and maintainable approach for managing states and transitions.
  2. Database Integration:
    • For high-volume systems, integrating with a database allows you to track each transaction, monitor usage patterns, and perform auditing. This would involve storing token usage, timestamps, and any error events.
  3. Message Queue:
    • To decouple the turnstile's operation from other systems (e.g., payment processing), a message queue (like Kafka or RabbitMQ) can be used. When a token is inserted, a message is published to the queue, and other services can asynchronously process it.

8. Edge Cases

  • Double Push: Handle the scenario where someone tries to push again when already unlocked (e.g., prevent multiple people from passing).
  • Invalid Token: Implement validation to ensure tokens are valid (e.g., check against a database or token service).
  • System Malfunction: Add a manual override in case of hardware or software failure.
  • Token Rejection: If a token is invalid or expired, provide feedback and prevent passage.
  • Concurrent Access: Implement locking mechanisms to prevent race conditions if multiple processes try to update the turnstile state simultaneously.

9. Future Considerations

  • Different Token Types: Support different types of tokens (e.g., RFID cards, mobile payments) by adding a token type attribute and handling each type accordingly.
  • Remote Unlocking: Implement a remote unlocking feature for authorized personnel via an API.
  • Audit Logging: Log all transactions (token insertions, pushes) for auditing and security purposes.
  • Analytics: Collect and analyze usage data to optimize turnstile placement and operation.
  • Integration with Payment Systems: Enable direct payment processing at the turnstile.
  • Scalability:
    • Load Balancing: Implement load balancing to distribute traffic across multiple turnstile instances.
    • Caching: Cache frequently accessed data (e.g., token validity) to reduce database load.
    • Microservices: Decompose the system into microservices (e.g., token validation, transaction logging) to improve scalability and maintainability.

By considering these aspects, the turnstile system can be designed to be robust, scalable, and adaptable to future requirements.