Skip to content

Latest commit

 

History

History
226 lines (167 loc) · 8.62 KB

design.md

File metadata and controls

226 lines (167 loc) · 8.62 KB

V1 Design

Introduction

Different considerations for the project are described below, and they're a good starting point to understand the goal and nature of the project. This includes the planned product, architecture considerations, principles, potential libraries and a rough idea about the API.

Internal quality in Software Development is cheaper in the long run [1]. This means that a high effort will be put in having a good foundation for the project, so that it's possible to iterate faster, cheaper and safer. The project embraces:

  • Unit Testing
  • Continuous Integration/Deployment
  • Metrics
  • Refactoring

Outcome

Architecture

The final version of Perfecty Push will extend the current capabilities offered by the plugin version. The development will be iterative and in general, it should:

  • Support both Push API and Websockets notifications.
  • Have a Push Engine independent of the notification mechanism.
  • Support user segmentation (device, location, engagement).
  • Optional user presence indicators.
  • Single binary and multiplatform: Windows/Linux.
  • Exponential Backoff for third-party integrations.
  • Unified JS SDK.
  • Administration UI.
  • Extendable using the Go plugin system

MVP

A potential MVP is the version that supports the same features as the plugin version, with basic user segmentation support, completely distributable in the WordPress plugin marketplace, and stable. This means:

  • Supports Push API initially
  • Single binary and multiplatform
  • Basic user segmentation (Excluding engagements and meta attributes)
  • Unified JS SDK

Architecture

In this Go project there are two possible paths, which will be tried both, in phases:

The first approach is more common in the Go/Elixir communities, while the second comes from the Java/.NET world for enterprise focused applications. There have been some intentions to apply the latter in the former, and some strong opinions from the community against that, so it's a good experiment for this project, in case we consider it's worth it:

"The purpose of a good architecture is to defer decisions", [*]

The key here is to embrace unit testing and refactoring.

Components

According to Clean Architecture, to guarantee a loosely coupled system we need to correctly apply the Dependency Inversion Principle and separation of concerns. A good initial architecture that could enable us to apply TDD and DDD in a future refactoring is:

  • Adapters - /internal/handlers/ and /internal/repository/: HTTP handlers and DB repositories
  • Application - /internal/application/: Use cases
  • Domain - /internal/domain/: Called Entities but in Domain Driven Design it's simply the "domain"
  • Frameworks&Drivers - All the rest: The outer layer that glues the next inner layer (Adapters).

Components

Project layout

Follows: https://github.com/golang-standards/project-layout

HA and Fault Tolerance

For the long run, it's expected that Perfecty Push supports High Availability and Fault Tolerance. This means implementing the features that allow multinode execution, concurrency and (eventual/strong <?>) consistency.

Libraries:

While Strong Consistency brings reliability, and a guarantee on the data when there are Partitions, it has an overhead in performance. In case the performance is more important, we should rely on Eventual consistency instead. Remember, we cannot have both (CAP).

Considerations

User segmentation

It's important to support user segmentation based on multiple parameters:

  • Device type
  • Browser
  • Location
  • Timezone
  • Engagement (depends on the User presence indicators described below)
  • Meta attributes set through the SDK (Don't implement it in V1 yet, but have it in mind)

User presence

We need to know the user presence by:

  • Measuring the time spent in the website.
  • Heartbeat mechanism to know if the user is currently connected. It should be optionally enabled.

Independent Push Engine

The push engine must be unaware of the notification mechanism. The specifics are implemented by the mechanisms, which take work from the Push Engine queue to use a back-pressure strategy.

Initially supported mechanisms:

This means we could later add others (APN, FCM).

JS SDK

It's the JS SDK's responsibility to choose between Push API or Web Sockets. Initially, Push API is tried, Web Sockets is the fallback.

The JS SDK will be based on the current implementation from the WP version. A separate project should be created and the quality must be improved.

Observability and distributed tracing

Metrics

Metrics are specially important for a fast Push Notification Server. In this case, we start from the baseline that the plugin version currently has a limit in this aspect and we need to measure the progressions in the development once we have a stable version. Use:

  • Golang benchmarking
  • Prometheus / InfuxDB
  • OpenTelemetry

Tracing

Structured logs are preferred over traditional logs. All the HTTP requests will use the same trace field populated in the context. In the case of push notifications, they will have an associated tracker field (user.id). Integration to the Opencensus telemetry framework should be considered.

Exponential Backoff with third-party integrations

The mechanisms that depend on third-party services like Push API or APN or FCM, should detect transient failures in the communication with the third-party. Errors that are not related to dead endpoints (403/410), should automatically increase the retry factor and stop at a maximum value.

If the maximum is reached, it's a symptom that either we have:

  • Provided the wrong VAPID credentials. This happens in some services that return 401 for endpoints that were expired, see this issue.
  • We have a failure in our side that we need to fix first (any miss-configuration or bug)

In those cases we don't want to re-try as it's considered a permanent failure that we need to address first.

Libraries:

Public Endpoints

endpoint method description
/v1/public/users PUT Register user
/v1/public/user/:uuid/preferences PUT Change the preferences (opt-in/opt-out)
/v1/public/user/:uuid/heartbeat PUT Let the server know when the user is using the app

Internal Endpoints

Users

endpoint method description
/v1/users GET List the users
/v1/users/:uuid GET Get the user
/v1/users/:uuid DELETE Delete the user
/v1/users/:uuid PUT Update user
/v1/users/stats GET Get the users stats

Notifications

endpoint method description
/v1/notifications GET List the notifications
/v1/notifications PUT Send a new notification
/v1/notifications/:uuid GET Get the notification
/v1/notifications/:uuid DELETE Delete the notification
/v1/notifications/stats GET Get the notifications stats