[Zubair Noman - 8/11/2022]
The article explains the choice of using the .NET platform at Populate and establishes .NET as an excellent choice for what we are building. This article does not attempt to prove that .NET is the only way to do it. Nor is any attempt made to do a language-by-language or platform-by-platform comparison.
What we wanted:
Great development tooling
Type safe general-purpose language
Substantial documentation and community support
Flexibility for growth and adoption
Best suited for Health Tech and Healthcare industry in general
What we intended to avoid:
An academic language
Package management nightmare
Low community support
Small recruiting pool
Exponential growth in complexity as the project grows
That's a broad list. But it can be distilled down to a representative summary statement - a powerful platform with great tooling that has widespread adoption and won't risk deprecation any time soon. Let's explore.
The primary language in the .NET ecosystem is C#. It is a powerful general-purpose language. The language features a static type system with generics, lambdas, LINQ, and asynchronous task composition, to name a few. Modern C# also employs many advanced features previously exclusive to functional languages. Pattern-matching, deconstructing tuples, and parameter null checking are the important ones. Though some of these features remain clunky in appearance (pattern matching, i.e.) relative to their implementation in F#, C# is getting better with more functional features with each upgrade.
The other languages in the .NET ecosystem are F# and Visual Basic. The existence of F# and C# in the same ecosystem and the interoperability between them in the same solution makes for a compelling case for .NET. A functional language such as F# brings unique advantages by being a concise language that handles concurrency out of the box in the form of an async programming model. We like to take advantage of all this wherever it makes sense within a well-defined context boundary. The platform seamlessly integrates multiple languages in a single solution and provides exciting opportunities to use the right tool for the job. A case in point is referencing generative type providers from F# and using the generated types in C#.
Why not use F# as the primary language? F# does not enjoy the widespread adoption of C#. The developer pool is relatively small. Dealing with persistence requires ingenuity. All put a ding on F# against choosing it as the primary language. But its strengths make it an attractive proposition to use in the proper context where it can shine.
One big issue in any ecosystem is package management. Package versioning and upgrade management can be a productivity killer. .NET Standard Library has nearly everything out of the box with 1st class support - making developers highly productive. Whatever else is needed can be managed through NuGet. Easier dependency management goes a long way in making a developer continue to work with high velocity when a project gets large. The .NET way of moving the base assemblies to the framework and keeping the management of the rest using NuGet is a step forward in the right direction.
On the ORM front, Entity Framework Core is a complete rewrite of its predecessor Entity Framework. While being a good ORM, the older EF suffered from poor performance, a problem primarily eradicated in EF Core. In many benchmarks, EF Core matches the performance levels of Dapper while providing magnitudes better developer experience when it comes to data mapping and complex query syntax.
Our choice of web framework is ASP.NET Core. The framework has a fast execution. Techempower.com article puts it in the top three in terms of response speed.
Developer tooling is a crucial aspect to consider from Day 1 and not as an afterthought. A well-designed toolchain works as a productivity tailwind, whereas the opposite can hinder it.
Visual Studio and Visual Studio Code are world-class IDEs, if not one of the best. It is fantastic for code search, code discovery, performance, and general user experience. Roslyn code quality and code style analyzers are on by default and allow for external analyzers such as StyleCop.
Source Generator runs during compilation and can inspect programs to produce files that can compile together. Source generators can replace some of the steps achieved through Reflection, a much slower process.
Distributed Tracing is handy in debugging difficult-to-track issues. A diagnostics client library allows writing a custom diagnostic tool that I plan to explore.
All in all, the .NET core provides excellent dev ergonomics and dev experience.
Text Analytics and Speech-to-Text are core building blocks of Populate. They play an integral role in the overall design strategy of the system. Doctors demand minimal touch points with their EHR system while experiencing a streamlined workflow that captures complete documentation coverage. Google, Amazon, and Microsoft provide excellent speech recognition and analytics AI modules. Microsoft offers a Cognitive Services module focused on extracting and labeling medical information from unstructured texts such as doctor's notes, discharge summaries, and clinical documents. I suspect Microsoft is a bit ahead on this front because they acquired Nuance, the maker of a speech recognition system that serves 77% of US hospitals.
Last but not least - Populate is a Health Tech company. The successful implementation of the product relies in no small part on interoperability with external systems. Some communication protocols are standardized in the industry to use the FHIR specification, but no governing body enforces it. The widespread adoption of FHIR is likely years out. In the meantime, the product will succeed through API integration, gRPC calls, and SDK consumption. .NET is widely adapted in Health Care and enjoys deep penetration. The existence of a .NET version, if there is an SDK, is almost guaranteed. Choosing a language deeply compatible with the technology in the industry we operate in is relieving.
Other powerful tech stacks considered:
Reasons that stopped us from using Golang
Lack of generics
Poor built-in exception handling
Limited experience within the team and the fastest path to MVP does not go through learning a new language
The true cost, which most inadvertently manifests in year 2 and beyond, is unknown
The small size of the potential hiring pool
Reasons that stopped us from using Node.js
The standard library is tiny; you need to pull in piles of NPM packages to do anything
With the above comes the challenge of package management, versioning, and the risk of package obsolescence
Many choices to do almost every single thing - more decisions to make at every step
Being single-threaded means not taking advantage of multi-core systems
Contrived developer productivity through a single language ecosystem is not a priority for us
Dealing with "async by default" everywhere, even where it's not needed