Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Design of Everyday APIs

The Design of Everyday APIs

What makes a good API for a library? Or more importantly, what makes it bad? This talk discusses the principles of what goes into user-centered design, and how best to apply those principles when writing a Python library for fellow developers.

Lynn Root

June 02, 2022
Tweet

More Decks by Lynn Root

Other Decks in Technology

Transcript

  1. The Design of


    Everyday APIs
    Lynn Root @roguelynn
    Staff Engineer @ Spotify

    View full-size slide

  2. $ whoami▮
    $ whoami

    View full-size slide

  3. – Don Norman, The Design of Everyday Things
    Design is concerned with how things work,
    how they are controlled, and the nature of the
    interaction between people and technology.

    View full-size slide

  4. – Don Norman, The Design of Everyday Things
    Design is concerned with how things work,
    how they are controlled, and the nature of the
    interaction between people and technology.

    View full-size slide

  5. – Don Norman, The Design of Everyday Things
    Two of the most important characteristics
    of good design are discoverability and
    understanding.

    View full-size slide

  6. 5 Key Elements of Discoverability

    View full-size slide

  7. 5 Key Elements of Discoverability
    ► Affordances

    View full-size slide

  8. 5 Key Elements of Discoverability
    ► Affordances


    ► Signi
    fi
    ers

    View full-size slide

  9. 5 Key Elements of Discoverability
    ► Affordances


    ► Signi
    fi
    ers


    ► Constraints

    View full-size slide

  10. 5 Key Elements of Discoverability
    ► Affordances


    ► Signi
    fi
    ers


    ► Constraints


    ► Mappings

    View full-size slide

  11. 5 Key Elements of Discoverability
    ► Affordances


    ► Signi
    fi
    ers


    ► Constraints


    ► Mappings


    ► Feedback

    View full-size slide

  12. Understanding

    View full-size slide

  13. Understanding
    System / Product

    View full-size slide

  14. Understanding
    Conceptual


    Model
    System / Product
    User

    View full-size slide

  15. Understanding
    Conceptual


    Model
    Conceptual


    Model
    System / Product
    Designer User

    View full-size slide

  16. Understanding
    Conceptual


    Model
    Conceptual


    Model
    System / Product
    User
    Designer

    View full-size slide

  17. Understanding
    User
    Designer
    Conceptual


    Model
    Conceptual


    Model
    System / Product

    View full-size slide

  18. $ ffmpeg -filters
    wat
    wtf is all this
    no...
    plz stahp...

    View full-size slide

  19. Good API Design

    View full-size slide

  20. Good API Design
    LYNN’S 3 TENETS TO

    View full-size slide

  21. 1 of 2
    class Message:
    def __init__(self, id: str, data: str, published_at: str) -> None:
    self.id = id
    self.data = data
    self.published_at = published_at
    Library

    View full-size slide

  22. class Message:
    def __init__(self, id: str, data: str, published_at: str) -> None:
    class PubSubClient:
    def __init__(self, topic: str, subscription: str) -> None:
    def create_topic(self) -> None:
    def create_subscription(self) -> None:
    def add_message(self, msg: Message, timeout: int, retries: int) -> None:
    def get_message(self, timeout: int, retries: int) -> Message | None:
    def mark_message_done(self, msg_id: str, timeout: int, retries: int) -> None:
    def clear_message_queue(self) -> None: pass
    def close_client(self) -> None: pass
    Library
    2 of 2

    View full-size slide

  23. 1 of 2
    client = chaos_queue.PubSubClient(TOPIC, SUBSCRIPTION)
    try:
    client.create_topic()
    except chaos_queue.TopicExists:
    pass
    try:
    client.create_subscription()
    except chaos_queue.SubscriptionExists:
    pass
    User

    View full-size slide

  24. 2 of 2
    for i in range(5):
    message = chaos_queue.Message(
    id=str(uuid.uuid4()),
    data=f"hello {i}",
    published_at=datetime.datetime.now().isoformat()
    )
    try:
    client.add_message(message, 1, 0)
    except (chaos_queue.TimeoutError, queue.Full):
    pass
    while True:
    message = client.get_message(2, 2)
    if not message:
    break
    print(message)
    process_data(message.data)
    client.mark_message_done(message, 0, 0)
    client.close_client()
    User

    View full-size slide

  25. for i in range(5):
    message = chaos_queue.Message(
    id=str(uuid.uuid4()),
    data=f"hello {i}",
    published_at=datetime.datetime.now().isoformat()
    )
    try:
    client.add_message(message, 1, 0)
    except (chaos_queue.TimeoutError, queue.Full):
    pass
    while True:
    message = client.get_message(2, 2)
    if not message:
    break
    print(message)
    process_data(message.data)
    client.mark_message_done(message, 0, 0)
    client.close_client()
    User
    2 of 2

    View full-size slide

  26. for i in range(5):
    message = chaos_queue.Message(
    id=str(uuid.uuid4()),
    data=f"hello {i}",
    published_at=datetime.datetime.now().isoformat()
    )
    try:
    client.add_message(message, 1, 0)
    except (chaos_queue.TimeoutError, queue.Full):
    pass
    while True:
    message = client.get_message(2, 2)
    if not message:
    break
    print(message)
    process_data(message.data)
    client.mark_message_done(message, 0, 0)
    client.close_client()
    User
    2 of 2

    View full-size slide

  27. for i in range(5):
    message = chaos_queue.Message(
    id=str(uuid.uuid4()),
    data=f"hello {i}",
    published_at=datetime.datetime.now().isoformat()
    )
    try:
    client.add_message(message, 1, 0)
    except (chaos_queue.TimeoutError, queue.Full):
    pass
    while True:
    message = client.get_message(2, 2)
    if not message:
    break
    print(message)
    process_data(message.data)
    client.mark_message_done(message, 0, 0)
    client.close_client()
    User
    2 of 2

    View full-size slide

  28. intuitive
    intuitiv

    View full-size slide

  29. Use Domain Nomenclature
    intuitive

    View full-size slide

  30. before
    class PubSubClient:
    def __init__(self, topic: str, subscription: str) -> None:
    def create_topic(self) -> None:
    def create_subscription(self) -> None:
    def add_message(self, msg: Message, timeout: int, retries: int) -> None:
    def get_message(self, timeout: int, retries: int) -> Message | None:
    def mark_message_done(self, msg_id: str, timeout: int, retries: int) -> None:
    def clear_message_queue(self) -> None: pass
    def close_client(self) -> None: pass
    Library

    View full-size slide

  31. before
    class PubSubClient:
    def __init__(self, topic: str, subscription: str) -> None:
    def create_topic(self) -> None:
    def create_subscription(self) -> None:
    def add_message(self, msg: Message, timeout: int, retries: int) -> None:
    def get_message(self, timeout: int, retries: int) -> Message | None:
    def mark_message_done(self, msg_id: str, timeout: int, retries: int) -> None:
    def clear_message_queue(self) -> None: pass
    def close_client(self) -> None: pass
    Library

    View full-size slide

  32. after
    class PubSubClient:
    def __init__(self, topic: str, subscription: str) -> None:
    def create_topic(self) -> None:
    def create_subscription(self) -> None:
    def publish(self, msg: Message, timeout: int, retries: int) -> None:
    def pull(self, timeout: int, retries: int) -> Message | None:
    def ack(self, msg_id: str, timeout: int, retries: int) -> None:
    def drain(self) -> None:
    def close_client(self) -> None:
    Library

    View full-size slide

  33. after
    class PubSubClient:
    def __init__(self, topic: str, subscription: str) -> None:
    def create_topic(self) -> None:
    def create_subscription(self) -> None:
    def publish(self, msg: Message, timeout: int, retries: int) -> None:
    def pull(self, timeout: int, retries: int) -> Message | None:
    def ack(self, msg_id: str, timeout: int, retries: int) -> None:
    def drain(self) -> None:
    def close_client(self) -> None:
    Library

    View full-size slide

  34. Clumsy Naming


    Hints at


    Clumsy Abstractions
    intuitive

    View full-size slide

  35. before
    Library
    class PubSubClient:
    def __init__(self, topic: str, subscription: str) -> None:
    def create_topic(self) -> None:
    def create_subscription(self) -> None:
    def publish(self, msg: Message, timeout: int, retries: int) -> None:
    def pull(self, timeout: int, retries: int) -> Message | None:
    def ack(self, msg_id: str, timeout: int, retries: int) -> None:
    def drain(self) -> None:
    def close_client(self) -> None:

    View full-size slide

  36. before
    Library
    class PubSubClient:
    def __init__(self, topic: str, subscription: str) -> None:
    def create_topic(self) -> None:
    def create_subscription(self) -> None:
    def publish(self, msg: Message, timeout: int, retries: int) -> None:
    def pull(self, timeout: int, retries: int) -> Message | None:
    def ack(self, msg_id: str, timeout: int, retries: int) -> None:
    def drain(self) -> None:
    def close_client(self) -> None:

    View full-size slide

  37. after
    class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def publish(self, msg: Message, timeout: int, retries: int) -> None:
    def close(self) -> None:
    class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def pull(self, timeout: int, retries: int) -> Message | None:
    def ack(self, msg_id: str, timeout: int, retries: int) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    Library

    View full-size slide

  38. Provide Symmetry
    intuitive

    View full-size slide

  39. class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def publish(self, msg: Message, timeout: int, retries: int) -> None:
    def close(self) -> None:
    class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def pull(self, timeout: int, retries: int) -> Message | None:
    def ack(self, msg_id: str, timeout: int, retries: int) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    before
    Library

    View full-size slide

  40. class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def publish(self, msg: Message, timeout: int, retries: int) -> None:
    def close(self) -> None:
    class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def pull(self, timeout: int, retries: int) -> Message | None:
    def ack(self, msg_id: str, timeout: int, retries: int) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    before
    Library

    View full-size slide

  41. class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self, msg: Message, timeout: int, retries: int) -> None:
    def close(self) -> None:
    class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, timeout: int, retries: int) -> Message | None:
    def ack(self, msg_id: str, timeout: int, retries: int) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    after
    Library

    View full-size slide

  42. class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self, msg: Message, timeout: int, retries: int) -> None:
    def close(self) -> None:
    class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, timeout: int, retries: int) -> Message | None:
    def ack(self, msg_id: str, timeout: int, retries: int) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    after
    Library

    View full-size slide

  43. Use domain
    nomenclature


    Clumsy naming hints at
    clumsy abstractions


    Provide symmetry
    Intuitive Tenet 2… Tenet 3…

    View full-size slide

  44. Provide Sane Defaults
    flexible

    View full-size slide

  45. class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self, msg: Message, timeout: int, retries: int) -> None:
    def close(self) -> None:
    class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, timeout: int, retries: int) -> Message | None:
    def ack(self, msg_id: str, timeout: int, retries: int) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    before
    Library

    View full-size slide

  46. class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self, msg: Message, timeout: int, retries: int) -> None:
    def close(self) -> None:
    class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, timeout: int, retries: int) -> Message | None:
    def ack(self, msg_id: str, timeout: int, retries: int) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    before
    Library

    View full-size slide

  47. after
    class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self,
    msg: Message,
    timeout: int = 30,
    retries: int = 0,
    ) -> None:
    def close(self) -> None:
    class SubClient:
    ...
    Library

    View full-size slide

  48. after
    class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self,
    msg: Message,
    timeout: int = 30,
    retries: int = 0,
    request_id: RequestIdType | bytes | str | None = None,
    debug: DebugLevelType | int | bool | None = None,
    ) -> None:
    def close(self) -> None:
    class SubClient:
    ...
    Library

    View full-size slide

  49. class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self, msg: Message, **kwargs) -> None:
    def close(self) -> None:
    class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message | None:
    def ack(self, msg_id: str, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    after
    Library

    View full-size slide

  50. Minimize Repetition
    flexible

    View full-size slide

  51. class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self, msg: Message, **kwargs) -> None:
    def close(self) -> None:
    before
    Library

    View full-size slide

  52. class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self, msg: Message, **kwargs) -> None:
    def close(self) -> None:
    before
    Library

    View full-size slide

  53. class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self, *msgs: Message, **kwargs) -> None:
    def close(self) -> None:
    after
    Library

    View full-size slide

  54. class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self, *msgs: Message, **kwargs) -> None:
    def close(self) -> None:
    after
    Library

    View full-size slide

  55. class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self, *msgs: Message, **kwargs) -> None:
    ...
    for message in msgs:
    self._queue.put(message, timeout=timeout)
    def close(self) -> None:
    after
    Library

    View full-size slide

  56. Be Predictable and Precise
    flexible

    View full-size slide

  57. class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message | None:
    def ack(self, msg_id: str, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    before
    Library

    View full-size slide

  58. class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message | None:
    def ack(self, msg_id: str, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    before
    Library

    View full-size slide

  59. class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message | None:
    def ack(self, msg_id: str, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    before
    Library

    View full-size slide

  60. class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message:
    def ack(self, msg_id: str, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    after
    Library

    View full-size slide

  61. class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message:
    ...
    try:
    return self._queue.get(timeout=timeout)
    except queue.Empty:
    raise ChaosEmptyError("Subscription queue is empty!")
    def ack(self, msg_id: str, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    after
    Library

    View full-size slide

  62. Let Users Be Lazy
    flexible

    View full-size slide

  63. class Message:
    def __init__(self, id: str, data: str, published_at: str) -> None:
    self.id = id
    self.data = data
    self.published_at = published_at
    before
    Library

    View full-size slide

  64. OptStr = str | None
    class Message:
    def __init__(
    self,
    data: str,
    id: OptStr = None,
    published_at: OptStr = None
    ):
    self.data = data
    self.id = id or str(uuid.uuid4())
    self.published_at = published_at or datetime.datetime.now().isoformat()
    after
    Library

    View full-size slide

  65. OptStr = str | None
    class Message:
    def __init__(
    self,
    data: str,
    id: OptStr = None,
    published_at: OptStr = None
    ):
    self.data = data
    self.id = id or str(uuid.uuid4())
    self.published_at = published_at or datetime.datetime.now().isoformat()
    def __repr__(self) -> str:
    return f"Message(id={self.id}, published_at={self.published_at})"
    after
    Library

    View full-size slide

  66. from dataclasses import dataclass, field
    @dataclass
    class Message:
    data: str = field(repr=False)
    id: str = field(default=None)
    published_at: str = field(default=None)
    def __post_init__(self):
    self.id = self.id or str(uuid.uuid4())
    self.published_at = self.published_at or datetime.datetime.now().isoformat
    option 2
    after
    Library

    View full-size slide

  67. from attrs import define, field
    @define
    class Message:
    data: str = field(repr=False)
    id: str = field()
    published_at: str = field()
    @id.default
    def set_id_default(self):
    return str(uuid.uuid4())
    @published_at.default
    def set_published_at_default(self):
    return datetime.datetime.now().isoformat()
    after
    Library
    option 3

    View full-size slide

  68. Use domain
    nomenclature


    Clumsy naming hints at
    clumsy abstractions


    Provide symmetry
    Provide sane defaults


    Minimize repetition


    Be predictable and precise


    Let users be lazy
    Flexible
    Intuitive Tenet 3…

    View full-size slide

  69. Provide Composable Functions
    simple

    View full-size slide

  70. class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message:
    def ack(self, msg_id: str, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    before
    Library

    View full-size slide

  71. class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message:
    def ack(self, msg_id: str, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    before
    Library

    View full-size slide

  72. class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message:
    def ack(self, msg_id: str, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    before
    Library

    View full-size slide

  73. class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message:
    def ack(self, msg: Message, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    after
    Library

    View full-size slide

  74. class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message:
    def ack(self, msg: Message, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    after
    Library

    View full-size slide

  75. class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message:
    def ack(self, *msgs: Message, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    after
    Library

    View full-size slide

  76. Leverage Language Idioms
    simple

    View full-size slide

  77. class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message:
    def ack(self, *msgs: Message, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    before
    Library

    View full-size slide

  78. class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message:
    def ack(self, *msgs: Message, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    before
    Library

    View full-size slide

  79. class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message:
    def iter(self, **kwargs) -> Iterator[Message]:
    ...
    while not self._queue.empty():
    message = self._queue.get(timeout=timeout)
    yield message
    def ack(self, *msgs: Message, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    after
    Library

    View full-size slide

  80. class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message:
    def iter(self, **kwargs) -> Iterator[Message]:
    ...
    while not self._queue.empty():
    message = self._queue.get(timeout=timeout)
    yield message
    def ack(self, *msgs: Message, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    after
    Library

    View full-size slide

  81. Leverage Language Idioms
    (again)
    simple

    View full-size slide

  82. before
    class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self, *msgs: Message, **kwargs) -> None:
    def close(self) -> None:
    Library

    View full-size slide

  83. before
    class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self, *msgs: Message, **kwargs) -> None:
    def close(self) -> None:
    Library

    View full-size slide

  84. class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self, *msgs: Message, **kwargs) -> None:
    def close(self) -> None:
    def __enter__(self) -> Self:
    return self
    def __exit__(self, *args) -> None:
    self.close()
    after
    Library

    View full-size slide

  85. class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self, *msgs: Message, **kwargs) -> None:
    def close(self) -> None:
    def __enter__(self) -> Self:
    return self
    def __exit__(self, *args) -> None:
    self.close()
    after
    Library

    View full-size slide

  86. Provide Convenience
    simple

    View full-size slide

  87. README.md
    ## Installation


    $ pip install chaos-queue

    View full-size slide

  88. ## Installation


    $ pip install chaos-queue
    ## Get Started


    import chaos_queue
    messages = [chaos_queue.Message(str(i)) for i in range(5)]
    pub_client = chaos_queue.PubClient(TOPIC)
    with pub_client.create():
    pub_client.publish(*messages, timeout=1, retry=False)
    README.md

    View full-size slide

  89. ## Installation


    $ pip install chaos-queue
    ## Get Started


    import chaos_queue
    messages = [chaos_queue.Message(str(i)) for i in range(5)]
    pub_client = chaos_queue.PubClient(TOPIC)
    with pub_client.create():
    pub_client.publish(*messages, timeout=1, retry=False)
    ## Learn more


    Just go to chaos-queue.readthedocs.io for documentation and more examples!
    README.md

    View full-size slide

  90. Use domain
    nomenclature


    Clumsy naming hints at
    clumsy abstractions


    Provide symmetry
    Provide sane defaults


    Minimize repetition


    Be predictable and precise


    Let users be lazy
    Provide composable
    functions


    Leverage language idioms


    Provide convenience
    Flexible
    Intuitive Simple

    View full-size slide

  91. ◄◄ REWIND

    View full-size slide

  92. before
    class Message:
    def __init__(self, id: str, data: str, published_at: str) -> None:
    self.id = id
    self.data = data
    self.published_at = published_at
    class PubSubClient:
    def __init__(self, topic: str, subscription: str) -> None:
    def create_topic(self) -> None:
    def create_subscription(self) -> None:
    def add_message(self, msg: Message, timeout: int, retries: int) -> None:
    def get_message(self, timeout: int, retries: int) -> Message | None:
    def mark_message_done(self, msg_id: str, timeout: int, retries: int) -> None:
    def clear_message_queue(self) -> None: pass
    def close_client(self) -> None: pass
    Library

    View full-size slide

  93. after
    @define
    class Message:
    data: str = field(repr=False)
    id: str = field()
    published_at: str = field()
    @id.default
    def set_id_default(self):
    return str(uuid.uuid4())
    @published_at.default
    def set_published_at_default(self):
    return datetime.datetime.now().isoformat()
    class PubClient:
    def __init__(self, topic: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def publish(self, *msgs: Message, **kwargs) -> None:
    def close(self) -> None:
    def __enter__(self) -> Self:
    def __exit__(self, *args) -> None:
    class SubClient:
    def __init__(self, subscription: str) -> None:
    def create(self) -> None:
    def delete(self) -> None:
    def update(self, config: dict) -> None:
    def pull(self, **kwargs) -> Message:
    def iter(self, **kwargs) -> Iterable[Message]:
    def ack(self, *msgs: Message, **kwargs) -> None:
    def drain(self) -> None:
    def close(self) -> None:
    def __enter__(self) -> Self:
    def __exit__(self, *args) -> None:
    Library

    View full-size slide

  94. before
    client = chaos_queue.PubSubClient(TOPIC, SUBSCRIPTION)
    try:
    client.create_topic()
    except chaos_queue.TopicExists:
    pass
    try:
    client.create_subscription()
    except chaos_queue.SubscriptionExists:
    pass
    for i in range(5):
    message = chaos_queue.Message(
    id=str(uuid.uuid4()),
    data=f"hello {i}",
    published_at=datetime.datetime.now().isoformat()
    )
    try:
    client.add_message(message, 1, False)
    except (chaos_queue.TimeoutError, queue.Full):
    pass
    while True:
    message = client.get_message(2, True)
    if not message:
    break
    print(message)
    process_data(message.data)
    client.mark_message_done(message, None, False)
    client.close_client()
    User

    View full-size slide

  95. after
    messages = [chaos_queue.Message(data=f"hello {i}") for i in range(5)]
    with chaos_queue.PubClient(TOPIC) as pub_client:
    pub_client.create()
    try:
    pub_client.publish(messages, timeout=1)
    except chaos_queue.TimeoutError:
    pass
    with chaos_queue.SubClient(SUBSCRIPTION) as sub_client:
    sub_client.create()
    for message in sub_client.iter():
    print(message)
    process_data(message.data)
    sub_client.ack(message)
    User

    View full-size slide

  96. Use domain
    nomenclature


    Clumsy naming hints at
    clumsy abstractions


    Provide symmetry
    Provide sane defaults


    Minimize repetition


    Be predictable and precise


    Let users be lazy
    Provide composable
    functions


    Leverage language idioms


    Provide convenience
    Flexible
    Intuitive Simple

    View full-size slide

  97. – Don Norman, The Design of Everyday Things
    If all else fails, standardize.

    View full-size slide

  98. rogue.ly/apis
    Thank you!
    Lynn Root @roguelynn

    View full-size slide