Skip to content

PSIDefenseUp/todo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 

Repository files navigation

TODO

This repository contains both the frontend and backend for a TODO app (in the todo_frontend and todo_backend directories respectively).

Each has their own README with details on setup and the like.

Setup

Setup instructions for each project are available in their individual README files:

  • todo_frontend/README.md
  • todo_backend/README.md

Highlights

Frontend

We have a frontend application using Vite + React in TypeScript. This truly single page application does not perform any sort of internal routing, but does support:

  • Viewing your tasks
  • Creating tasks
  • Deleting tasks
  • Editing tasks (i.e. the title of the task)
  • Filtering out completed tasks
  • A global concept of user identity (based on a mocked up constant JWT here to avoid getting too deep)
  • An admin-facing spoofing feature
  • Support for setting a separate target user (though not notably used here, would scale to easily support something like viewing others' tasks without edit permissions by navigating to another user's page via URL)
  • API calls that include user auth
  • An API client that includes support for configurable retry + backoff strategies as well as configurable timeouts
  • Handling of different API errors (i.e. not retrying 4XX errors while retrying others)
  • Separation between view and API models
  • A couple "common" UI components

Backend

We have a backend application using .NET Core's "minimal" APIs. This was something new to me (I'm used to the older .NET and .NET Core MVC approaches), so I figured I'd give it a shot. This integrates with SQLite as suggested. Here we have support for:

  • Distinct separation between application layers (Program, API, Domain, and Persistence), where each has distinct models and roles generally revolving around the Domain at the core -- allows clean separation of concerns.
  • Support for User Authentication via JWT (though with validation disabled here to support our mock token from the frontend, we are receiving a token from the frontend and reading from it)
  • A handful of basic CRUD operations for Tasks (often "TodoTasks" to avoid overlap with the C# Task class) -- creation, (soft) deletes, other updates
  • Integration with a local SQLite database for persistence
  • Support for linking particular domain exceptions with appropriate HTTP response codes if unhandled (i.e. 401 unauthorized, 404 not found, 500 anything not explicitly handled)

Trade-offs, assumptions, and thought process

Frankly when I saw the extent of the assignment was just "build a to-do application" I had no idea how in-depth I was going to go. I haven't used any sort of to-do app myself, usually opting to track that sort of thing via a text document that usually looks something like:

- [ ] DO THE THING
- [~] THE NEXT THING
	- [X] SOME PART
	- [ ] ANOTHER PART
		- this means do that thing, you know
- [X] BIG THING 3
- [~] THING 4

Originally I started with a model that looked similar to the above for a Task (using TS for simplicity):

interface Task {
	id: number;
	parentId: number | null;
	title: string;
	description: string;
	status: TaskStatus; // NOT_STARTED, IN_PROGRESS, COMPLETED
}

I went ahead and built out the basic backend first, trying to figure out how to hook up SQLite and approach organizing these Minimal .NET Core endpoints, and ran with the above model for a while.

After getting the backend in working condition, I started to put together the UI so that I could get things working end-to-end.

Doing some quick searching around online it seemed like a "TODO" app was a pretty common basic project and usually wouldn't focus so much on user-facing niceties so much as proving out basic concepts. I figured I'd focus in that direction as well -- trying to offer something that, if simple, had enough well-engineered foundations and patterns that could be built on top of. After getting some basic UI in place, it seemed support for some of the nicer features supported by the above model would require inordinate effort that might take too much time for a limited test like this and distract from the strong foundations I was looking to build.

I pared the Task down to the basics and focused on buliding out a strong baseline of general features that applications I've worked on have tended to support, and of those the ones that were more interesting to get right. This included things such as:

  • robust communication between the frontend and backend with support for retries
  • getting some notion of the user identity through to build scaffolding for Authentication and Authorization
  • building some basic admin tooling that generally serves as foundational for any user-facing application
  • domain-driven logic in the backend including the beginnings of transforming domain exceptions into API-level errors (ex: the UnauthorizedError triggering a 401)

Ultimately we ended up with a Task model that looks like this:

interface Task {
	id: number;
	userId: string;
	title: string;
	isCompleted: boolean;
	isDeleted: boolean;
}

While there aren't as many nice user-facing features available with something like this, it's not as if it would be impossible to add such things on in the future. They would just require some additional UI work that I didn't want to invest more time in.

My assumptions included:

  • I should, at minimum, offer a tool for a user to manage a list of their own items
  • We'd be fine serving users a single, flat list of items for the time being
  • Offering users the ability to clean things up would be helpful, however, so supporting deletion as well as a basic filter would be easy wins
  • We don't have a need for any service-to-service bulk operations, so the backend can serve mostly one-off calls based on individual user actions in the UI
  • We would want to support multiple users from the service's perspective, so we'd like some notion of user identity both linked to tasks and from the UI
  • Were this a production application, we would like the ability to view and test the system from the user's perspective as admins
  • Building a strong foundation and establishing architecture and patterns for different interactions would be most interesting from your perspective
  • Some missing UX polish would be acceptable for this test (ex: not having visible error state when an update operation fails) given fixes would be measuring UX design and time spent moreso than an eye for the issues or technical acumen (especially if I call such gaps out here!)

Scalability

I think the biggest notable gaps to me are:

  • The backend relies on SQLite for persistence
    • Not exactly going to scale horizontally across additional hosts when we store our data locally, even if it might scale vertically to some degree
  • APIs do not support batch operations
    • Not particularly relevant given the limited frontend functionality at the moment, but this does result in more individual calls being made and taking up server threads however briefly, and SQL is pretty good at handling a number of batch operations
  • APIs do not support pagination
    • For relevant APIs (i.e. ListTasks, ListUsers), we just grab everything and send it. Obviously this doesn't scale to a large amount of data and we'd like to impress limits on how much data we're working on per request in our API. This would keep performance more consistent across individiual calls and avoid the ability for a small number of users to exhaust the service's ability to serve traffic by issuing long-running requests.
  • We don't have any sort of rate limiting configuration on the backend that allows it to limit impact from troublesome users
  • The lack of metrics in the backend application means we have limited visibility into how the system is performing -- a bit critical when we're questioning how well the system scales given metrics provides a means of measuring that
  • Not necessarily the usual interpretation of scalability (i.e. to more users and/or data), but the API currently supports user-based authentication and would need a separate path established for service-to-service auth were that desired in the future

Some of these I opted out of focusing on here to avoid the need to dump an inordinate amount of time into a test project, but I generally chose to cut those that were less immediately interesting (i.e. to me they have relatively straightforward solutions that can be built on top of what I have put together and were less foundationally critical).

Future Enhancements

Aside from features that were implemented in a half-mocked fashion (ex: authentication, spoofing, authorization), there are a number of features that I did not get to including even in such condition. Many of these are fundamental to any production application.

Such enhancements would include:

  • Tests. I did not add unit tests here (or, obviously, integration tests) for either the frontend or backend, but these would be a quick win to ensure sanity when looking at an actual production application.
  • [Frontend] Addition of telemetry
  • [Frontend] More polished loading states and error handling (ex: update operations that hit API errors do not report visible error state to users, loading states often just look like "LOADING..." or blank and have visible UI shift)
  • [Frontend] Increased support for per-API configuration on the API client (ex: the ability for a caller to specify a different timeout when calling an API versus having one global timeout configured on the HttpClient)
  • [Backend] Emission of custom metrics (ex: how are our endpoints performing, how are our dependencies performing, any additional things we may be interested in monitoring such as access patterns)
  • [Backend] More scalable APIs for List* endpoints (i.e. paginated endpoints to better scale with more data)
  • [Backend] Standardized response structure -- it makes things much easier for consumers when the basic shape of a response is consistent across APIs and across success/error states
  • [Feature] I thought it would have been nice to add a grouping / parenting feature, but pared it back here. Whether that's the ability to attach tasks below others or to offer users distinct groups of tasks they can view separately, or ideally both.
  • [Feature] I also thought having an "in progress" status would have been nice, but I ended up stripping out support for anything other than binary complete vs incomplete just to avoid spending too much time on things

About

TODO: describe

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages