This is an archive of the source code behind LaunchReady, a software product I founded and launched, but shuttered a few years ago so I could focus on another role that had come my way.
Licensing: This software is shared for portfolio and educational purposes. All rights to modify, reuse, or redistribute the code are reserved. I am open to individual licenses, reach out to me to chat.
This is a startup codebase, so it's not as polished as it would be if things were further along, but if you've built as many CI/CD pipelines, overhauled as many brownfield products, etc. as I have, you tend to wire in some things even at an early stage that may be missing in other 0-to-1 projects.
.circleci
and database
folders (plus backend/GDB.Tools.DatabaseMigration
using DbUp) are the only way this code touched productionThis is a multiplayer business and finance app. That means the core screens (the business model plan, task tracking, massive financial planning workbook) are all realtime updated using custom CRDTs. An end user jumping on a call with a teammate or advisor can work collaboratively while chatting, and the charts, dashboard, and other tabs will update in realtime.
The main components of each realtime collaborative page are:
cashForecastLocalStore
: a local store that projects remote and pending local events to display the current stateHere's how it all works (the tests here may be valuable)
Index.svelte
initializes the data store: eventStore.initializeseqNo
already used by this user's actorId
so it can produce event versions autonomously without repeating past used valueseventstore.loadFullState
and cashForecastApi
At this point, the screen is fully loaded and waiting for the user to make changes OR updates to come in from a filtered <WebSocketChannel>
(a custom component wrapped around SignalR channels).
actorId
and seqNo
, then added to an updated pendingEvents
store propertypendingEvents
:localStore
receives the updated pendingEvents
and projects a fresh final stateThe service is self-healing. If the server send fails, the pending events continue to queue and retry and the user can continue working.
As the server receives updates, it mildly breaks the CRDT pattern, borrowing from OT to apply a single version number to each event to simplify conflict management based on order of reception by the server. It then sends up updates view SignalR/WebSockets to every subscribed browser.
receiveEvent
in the cashForecastEventStore
to process incoming events:loadSinceEvents
which uses the cashForecastApi
to ask the server for all events newer than version X and then processes themstate
and pendingEvents
store propertiesIn this way, we are constantly projecting what the user expects to see, but also factoring in other edits streaming in from the server in an appropriate order, always applying versioned events and then un-versioned pending events.
Additionally, we're using immutable data properties to ensure we're only doing the math heavy financial projections when something actually changes.
The math for the cash forecast is particularly intense, modelling assets, expenses, taxes, revenue, plus complex waterfalls of loan pay back, revenue sharing, and more.
Modeled from a real publisher agreement: 100% of gross after distribution until their initial investment is matched, then a smaller percentage of the next $10MM
The backend is running .Net Core. This service was designed to deploy as a single web service, simplifying auth between the front-end and backend. However, Microsoft chose to change how front-end and back-end projects work together by default, to a model that favors separate deployment of the two.
Unlike most .Net apps, on startup this app:
All traffic is sent to the back-end, which proxies those calls to the JS dev server when running locally so we get the benefit of live changes without rebuilding or refreshing.
Mentioned earlier in the "Multiplayer" section, this backend has a number of API endpoints to serve the front-end. These are covered in integration tests that reference a secondary test database, allowing us to reset and add specific data to the database, call a method, then clean up, all without breaking data we have in our own local development db.
[Test]
public async Task GetLatestSeqNoAsync_ValidActorAndUser_ReturnsLatestSeqNo()
{
var user = _sampleUser;
var actor = Database.Actors.Add("actor-1", user.Id, 123, DateTime.UtcNow);
var result = await _controller.GetLatestSeqNoAsync(actor.Actor);
result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().BeOfType<LatestSeqNoModel>()
.Which.SeqNo.Should().Be(123);
}
Example test for the Actor endpoint
Additionally, when these tests are run they automatically run the database migrations against the test database to ensure it's up to date.
Note: this project does not use WebApplicationFactory, but here's an example of ASP.Net integration testing via WebApplicationFactory on my site
The database migrations are designed to run in a roll-forward manner, and the CI/CD deployment runs in this manner:
The migrations are intended to be run both automatically from our code in the Startup and Tests above (raising exceptions), but also as a command-line call during the CI/CD pipeline (outputting error codes), so there are two entry points that must operate the same.