LearningBuilder Development Experience
If you're reading this page then you've either applied for a position on our product team, or you're thinking about it. Welcome!
This page provides detailed information about our technical stack and development environment. These details will, obviously, have a big impact on what day-to-day life on our team is like and it's only natural that you'd want to know about them in advance.
Software is an ever-changing field and some of these details may have changed by the time you read this!
Who we’re looking for
We are looking for smart people who enjoy complex problems, learning new things, working with others, and who have high emotional intelligence. We like to get things done while taking care of each other and joking around in the process.
The ideal candidate “gets” software. They care about the design of the code, its resiliency in production, the interface it provides to other programmers, and about solving the business need.
The ideal candidate has strong opinions, weakly held; they have a reason for their opinions, but are able to modulate those opinions in the face of new information.
If that sounds like the team you want to join, then talk to us!
Development Process
We like to think of ourselves as "small 'a'" agile. We believe in the core tenets of the Agile Manifesto but are not strict followers of any specific methodology, and we shift our process based on business needs.
Sometimes we follow a regular release cadence, using a form of Kanban to track work.
Other times we work in two-week iterations, influenced by Scrum.
In all cases, we believe that cross-functional teams, working in collaborative environments and with short feedback cycles, is the key to building quality software and maintaining a stable velocity over time.
Programming Languages / Platforms / Architectures
LearningBuilder consists of:
A large, reasonably well-organized monolith containing the core logic and web UI
A small (but growing) set of AWS Lambda functions for integrations and extensibility
The monolith was originally written in .NET Framework using ASP.NET MVC. All new UI features are being written in Vue.js and we are in the process of upgrading to .NET 6/.Net Core.
We use:
ASP.NET MVC (.Net Core upgrade in progress)
Vue.js (plus some legacy code in jQuery that is slowly being migrated)
SQL Server 2016+ (transition to 2022 is in progress)
SQL Server Service Broker / RabbitMQ / AWS SQS for messaging
ElasticSearch, for publicly-available search features
SASS, to take some pain out of CSS
Ruby and Powershell, to automate our development process (you won’t be coding in these very often)
AWS for deployment, with growing use of Lambda functions for custom integrations
Developers and testers run the full stack locally (except for the cloud pieces). None of that “shared development database” nonsense here!
Data Access
All database schema changes are fully scripted (using a homegrown utility) and are source-controlled like everything else. We maintain a shared database snapshot in a known good state so that you're only ever one command-line tool away from restoring a local database with staged data.
Our legacy features use an ORM called NHibernate to interact with the database. Queries are typically expressed using the NH LINQ provider, but when necessary we'll hand-roll a query to optimize performance.
We also work with a lot of stored procedures; LearningBuilder is a very configurable product, and sprocs are often used as a way of injecting client-specific business logic into pre-defined "hooks" in the app.
Automated Testing
Automated testing is a big deal on our team. We switch between TDD, "test-first", and "test-after" workflows depending on the nature of the changes we're making. Regardless, the goals are always to:
Prove our code does what we expect it does
Document our intent to aid other programmers
Prevent regressions and aid in issue triage
As of this writing we have many thousands of unit tests and many hundreds of automated UI tests. These tests can be executed via the command line, via the NUnit test runner, or within Visual Studio using R# or TestDriven.NET.
We have invested heavily in a library of "data helpers" that assist with setting up test data, ensuring that it remains fairly easy to write new automated tests even as the product complexity continues to grow.
Source Control / CI
We use Git and Bitbucket.
Our general pattern is that most tasks are performed in feature branches and then merged into trunk once they are stable and are code-reviewed. We maintain release branches so that critical changes can be merged across multiple lines of development if needed.
The entire test suite is automatically executed for each check-in to trunk, and we will often set up CI builds to monitor long-running feature or release branches as well.
Deployments
When we’re ready to do a release, the product team uses some command line utilities to generate a “release package”, which is just a Nuget package that gets pushed into S3.
Our deployment team takes it from there, using a combination of Octopus Deploy and some bespoke utilities to roll that out to various servers.
Technical Principles
In addition to the core corporate values (outlined below), the technical team believes in the following:
"Go slow to go fast". We are interested in long-term stable velocity over time. We achieve that by paying attention to fundamentals, writing clean code, and sweating the details.
Diversity of thought / consistency of output. We value different ideas and problem solving strategies, but believe that consistently-applied patterns helps us manage complexity.
Always Build Components. We like to build product features on top of reusable components, patterns and frameworks that minimize and clarify the feature-specific code. We invest in helpers, wrappers, utilities, etc to eliminate as much boilerplate and repetition as possible.
Automate all the things - ain't nobody got time to be doing repetitive tasks by hand!
Innovate deliberately and intentionally. We all love shiny new stuff, but not every task is the right time to introduce brand new ways of doing things. But when it is the right time, then look for the evolutionary leaps and not the incremental shifts.
"It's better to build half a feature than a half-assed feature." (See "go slow to go fast")
Scope is negotiable; cleanliness is not. Nothing is more important than writing clean code, that clearly expresses its intent, and is as simple as it can be, because that is the foundation upon which all future progress will be made. (See "go slow to go fast")
Core Values
Heuristic Solutions has the following core values:
Pioneering - success often means heading out into the unknown. But don't just take risks willy-nilly; take calculated risks.
Act intentionally - make decisions about what you do. Think heuristically. Anticipate the future. Leave breadcrumbs.
Achieve excellence with others - teamwork matters. Engage others. Crave accountability.
Show that you care - companies are made of people, not "resources". We build strong teams by caring about each other as people first, and coworkers second.
These aren't just words on a motivational poster in the corner; they are things that we truly believe in, and you'll hear them frequently as we do our work.