-
Notifications
You must be signed in to change notification settings - Fork 5
Architecture
The JustFix.nyc Tenant Platform (sometimes called "tenants2") was designed with the following principles:
-
Progressive enhancement. JustFix services are used by tenants in a wide variety of devices and contexts that are difficult to predict. For example, many low-income individuals may need to use our services on computers with ancient browsers in housing court, or they may use older cell phones with outdated browsers.
Due to this, we strive to build the platform in such a way that a baseline experience works on virtually any browser, and may optionally become progressively enhanced to harness the capabilities of the user's device and network connection.
-
Seamlessness. End-users should not experience layout instability or "jank" when using the site. For example, the initial page load should not result in different pieces of content popping in or out of view, potentially derailing a user's train of thought and causing anxiety.
-
Accessibility. The platform should be usable by anyone, regardless of any visual, motor, or cognitive impairments.
This means, for example, that the site needs to be fully usable via the keyboard, screen readers, or other assistive technologies (ATs).
-
Stability. A suite of unit and integration help ensure that nothing accidentally breaks when we change things.
Furthermore, wherever possible, we use tools like TypeScript and mypy to ensure that the site doesn't accidentally crash. These tools also allow us to more easily understand and refactor the codebase.
-
Resiliency. The platform is designed to work when third-party services may be down or unavailable. As a corollary, whenever possible, integration with third-party services should be optional, rather than required.
-
Security. The platform should be resistant to malicious attacks through the use of best practices such as Content Security Policy. Sensitive data should be protected by two-factor authentication, and other measures should be taken to ensure that users and their data are safe.
To illustrate some of the core principles in action, we can examine the address field used by the platform.
If the user’s JavaScript and the NYC Planning Labs GeoSearch API are working, we present the user with a progressively-enhanced auto-complete experience that is made accessible to assistive technologies via the excellent Downshift component:
However, if either one of these prerequisites isn’t met--or if a network error occurs while the user is using the widget--the user sees the baseline user interface, which involves a non-autocompleting address field and an additional set of radio buttons asking for the user’s borough:
Additionally, because geocoding needs to be done after the user submits the form, if the geocoded address is significantly different from the one the user entered, a modal is shown that allows the user to confirm that the geocoded address is correct.
Sometimes core principles can be at odds. For example, consider the hamburger menu at the top-right of the page on mobile devices. When the page first appears, we'd like the menu to be collapsed:
Once the user activates the menu, it should expand:
However, our principle of progressive enhancement dictates that we shouldn't assume the user has JavaScript available, which means that, in order to for the content of the hamburger menu to be accessible, the hamburger menu needs to be expanded before the page has loaded and activated its JavaScript.
However, this violates our principle of seamlessness, as many users loading the page will see the hamburger menu expanded for a split-second and then immediately collapse. That's janky.
One way to balance these conflicting principles is by recognizing that the vast majority of users will have JavaScript available. If we optimize for this case (by displaying the hamburger menu collapsed while the page is loading) but allow users who are left behind to indicate that things aren't working for them, we can potentially preserve both principles.
We currently do this by leveraging a feature we call compatibility mode. This feature is intended as a general-purpose resiliency mechanism in the case of unexpected JavaScript errors, but we can also use it to balance progressive enhancement with seamlessness.
By default, the user isn't in compatibility mode, so we send initial page content assuming that JavaScript will eventually load in the user's browser. However, if the user's browser has JavaScript disabled or our main JavaScript bundle doesn't load, the user will see the following at the bottom of their screen:
If they click "Activate compatibility mode", the page will re-load, but with the hamburger menu (and possibly other page elements) expanded, allowing the user to access the full functionality of the site.
The architecture consists of the following parts:
-
The user's browser only needs to be capable of rendering HTML and submitting forms via standard HTTP GET and POST. This is the peanut from Aaron Gustafson's M&M metaphor for progressive enhancement.
If CSS is available and has been delivered to the browser successfully, the page's content and core functionality is embellished with visual styling--the chocolate slathered on the peanut, as Gustafson would say.
Finally, if JavaScript is available and has been delivered to the browser successfully, the experience is further enhanced with additional interactivity. Some fields are embellished with autocomplete functionality; forms are submitted asynchronously via GraphQL, and page transitions make it easier for users to have a mental model of their experience. In Gustafson's metaphor, this is the sugary candy shell our confection is encased in.
In technical terms, the platform behaves as a standard "Web 1.0"-style application by default, but transforms into a single-page application (SPA) if the browser has the capabilities. A notable exception is if the user is having technical issues with the site, in which case they may opt-in to compatibility mode, which forces the app back into "Web 1.0" behavior.
-
The Django server responds to all requests from the user's browser. We use a wide variety of Django's features, relying heavily on its model and form validation frameworks, as well as its administrative user interface.
-
The JSX rendering service, sometimes called "lambda", is responsible for rendering the user's intial page content (this mechanism is sometimes referred to as "server-side rendering", or SSR). This helps ensure that the user's experience is seamless, and also makes it possible for the site to work without JavaScript on the client-side.
At present, this service is spawned as a subprocess by the Django server, rather than being a separate server accessed over the network (though it may migrate to the latter someday).
-
The Postgres database stores all user data.
Note that it is independent from an optional, secondary database containing open data about NYC (NYCDB), which the platform uses to gain insight about the user's housing situation (such as who the user's landlord is, given their address).