Building Production Ready Applications

Writing software can be hard, writing quality software is even harder.

Scott Kuhl
8 min readJun 26, 2021
Concept of data analysis and maintenance Illustration by WOOBRO DESIGN on Iconscout

I tend to follow the same guidelines and steps with every application I write from small apps to large enterprise systems. By following these guidelines, it improves my chances of creating a maintainable and stable application. So, I thought it was about time I write them down and share them.

You can use this checklist to stub out the initial technical requirements in GitHub Issues or a Kanban board. Even if you start your application based on an online tutorial or course, you can use this checklist to go back and fill in the gaps.

My background is in C# and .NET, so the examples here will be focused on that. But most if it applies to other languages and ecosystems as well.

Common Checklist

☑ Clean Code
☑ Documentation
☑ Architecture
☑ Testing
☑ Error Handling
☑ Logging
☑ Validation
☑ DevOps
☑ Application Monitoring
☑ Support Channels

Common (Optional)

☑ Feature Flags
☑ Profiling
☑ Mapping

Server Checklist

☑ Configuration
☑ Data Storage
☑ Data Seeding
☑ Querying
☑ Analytics
☑ Abuse

Server (Optional)

☑ Email
☑ Identity
☑ Exporting
☑ File Storage
☑ Background Services
☑ Real Time Communication
☑ Audit Trails

Client Checklist

☑ Localization
☑ Application Icons
☑ Service Access
☑ Responsive Interface
☑ Accessibility
☑ Forms
☑ Regulations
☑ Shell Integration
☑ Web Support

Client (Optional)

☑ Offline
☑ Data Virtualization
☑ Themes
☑ Printing
☑ SPA Support
☑ Third-Party Clients

Common Checklist

These are the conventions to apply to every application, whether they are running on the server or the client.

Clean Code

Clean code is a big topic by itself. But you can get yourself off on the right foot with linters / analyzers. For .NET I add the following to all my projects:

  1. A starting .editorconfig file.
  2. Roslynator — A extended collection of Rosyln analyzers.
  3. AsyncFixer — Finds and corrects common misuses of async/await.
  4. Enable Null Safety.
  5. A Guard Clause library — Helps for defensive programming by validating input parameters.

There are other options available to Visual Studio users. This means every developer must have it installed and its only available in Visual Studio, not Visual Studio Code or Visual Studio for Mac, which limits its appeal in team environments.

  1. CodeMaid — This Visual Studio add-in brings code cleaning, formatting, and organizing to your files. The configuration can be store in the solution.
  2. Code Cleanup on Save — Use this extension to apply code cleanup rules on every save. The cleanup profile used has all options applied. Unfortunately, at this time, these options can’t be stored in a solution.

An alternative that provides similar functionality is JetBrain’s Rider. It’s cross platform which opens it to more developers, but it's not free and locks everyone into the same IDE if you want to rely on it.

Documentation

At a minimum, create a README.md file with the following:

  1. A description of the project.
  2. How to get your project up and running.
  3. Guidelines for contributing code.
  4. Your clean code standards.
  5. A high-level description of the architecture.
  6. The features you have implemented in your code. These are not business rules, but technology features like logging and null safety.
  7. Learning resources for the major technology included in your project.

You should also consider creating a more complete Wiki over time.

Architecture

Decide on basic architectural patterns for your application and stick with them to make the application consistent. For a small application, a service layer for CRUD commands is usually enough. For larger applications I like to use the Clean Architecture Domain Drive Design patterns. For the largest and most complex applications consider a micro services approach. Serverless is also another popular option.

Testing

Do not skip automated testing. You should at least have unit testing, but you may want to go further and include integration, system, end to end, performance, penetration, etc.

My test projects tend to follow these patterns:

  1. xUnit based.
  2. Organized in folders that match the project namespaces.
  3. Use a Given / Should naming convention. Ex: Method_GivenSomeCondition_ShouldReturnThisValue.
  4. Follow the Act, Arrange, Assert pattern.
  5. Use Fluent Assertions for readability, which also come with an analyzer to make sure you are using them correctly.
  6. Add a mocking library. I prefer NSubstitute.

Error Handling

Your application should never hard crash. Make sure you catch all your errors, log them, and display a helpful error message. This is true if the error happens on the server or the client. A global error handling strategy is necessary.

Logging

Every application needs to log events. Consider Serilog for this. Make sure logging is happening on the server and the client. Serilog has “sinks” you can setup to record your logs to files, the Event Manager, a database and Application Insights.

Validation

Don’t let bad data into your app and don’t let bad data produce an error. All data coming in should be properly validated and report back if the data is not valid before it can be saved.

In .NET this is usually achieved using Data Annotations or Fluent Validation.

DevOps

Setup your code repository to compile your code and execute your tests automatically for pull requests with something like GitHub Actions. Then setup a strategy for deploying your code to users, if necessary, with something like Visual Studio App Center.

Application Monitoring

Don’t let problems with your application go unnoticed. Build in application monitoring from the start. Something like Application Insights works well for this.

Support Channels

Plan ahead for how your users will contact you for support. Will they simply use an email address? Or do you want more interaction through GitHub Discussions, a Discord server, social networks, or forums?

Common (Optional)

Feature Flags — At some point you may want to roll out a feature slowly and deprecate code over time. Feature Flags are an effective way to manage this.

Profiling — Want to know what’s taking so long on a specific page or action? Profiling is your ticket. There are tools baked into IDEs to help with this or you can setup automated tests to keep an eye on it. But if you want an integrated profiler testers and developers can access, look for something like Mini-Profiler.

Mapping — If you are copying data between object frequently such as between entities and view models, you should invest in an object mapper like AutoMapper.

Server Checklist

These are the conventions to apply to server-based applications like APIs.

Configuration

Setup your configuration settings so they can be specific to the environment they are running on. And store any sensitive information like passwords and keys in secure store like User Secrets or Azure Key Vault.

Data Storage

Will you support more than one configurable database type like both SQLite and SQL Server? Will you use an ORM like Entity Framework? Should you enable or disable cascading deletes?

Data Seeding

Load your application when it is running in development and test environments with sample data so you can better see how your application looks and performs from a non-blank slate. Consider Bogus for this.

Querying

Whether your application is server-side pages like MVC, or an API, you need a strategy to allow the controllers to query data from inbound parameters. This usually includes paging, searching, sorting, and filtering. You can manage this yourself or turn to a library like oData or GraphQL.

Analytics

Know how your application is being used beyond basic logs. Application Insights works well for this as already mentioned above for Application Monitoring.

Abuse

How will you protect your interface from clients sending in too many requests? Do you need some sort of rate throttling?

Server (Optional)

Email — You may need email when you are running a server. You can certainly send them through an email services like Outlook, but you should also look at a dedicated service like SendGrid. You can make sending emails easier with a package like FluentEmail.

Identity — Authentication is hard. Microsoft has baked it into its templates and there are a lot of third party options. You need to handle registration, login / logout, role or claims based authorization, token refreshing, password resets, email verification, user lockout, two-factor authentication, social logins, QR codes, biometrics, and more. Did I mention that authentication is hard?

Exporting— Sometimes your uses just want to take their data with them in Excel, Word or PDFs. Make it easy.

File Storage — File storage is different from data storage. In general, you don’t want to store them in the database. This is what file systems and Blob storage are for. Don’t forget to have a cleanup strategy for temporary or abandoned files if necessary.

Background Services — If you need something running in the background kicking off jobs look to Hangfire or Azure Functions.

Real Time Communication WebSockets and SignalR are your friends.

Audit Trails — In some cases, usually multi-user applications, you may want to store a record of every change that is made and possibly be able to undo changes based on the trail. This can be done with custom code or using a feature of the database such as Change Data Capture or Temporal Tables.

Client Checklist

These are the conventions to apply to client-based applications and web pages.

Localization

Not everyone speaks your native language. If you think there is a chance you need to support multiple languages, put in good globalization practices right away.

Application Icons

Don’t use the default generic icon. Get a custom icon and make sure you set them for whatever environments you need to support, like favicons and home screens.

Service Access

If you are going to access an API or any other data source that is external to the client, build in fail safes when they are slow or down. Polly works well for this.

Responsive Interfaces

Evaluate your application on a variety of screen sizes and types. Going beyond standard screens like TV and Dual Screen can make your application stand out.

Accessibility

Don’t forget your users that might have different abilities and needs.

Forms

Forms are easy and hard. Make sure you have a good client validation strategy, you prevent double posting, manage dirty form navigation, and prevent overcommunicating with services on controls like auto complete.

Regulations

Does your application live in an industry that is regulated? Know those regulations and make sure your application meets them. GDPR is a common example.

Shell Integration

Look for ways to make your application take advantage of the client’s native services like Notifications, 3D Touch, Timeline, Widgets and Live Tiles. Even simple things like the window title or proper page titles on the web.

Web Only

Errors — Add a custom error, not authorized, and not found page.

Lighthouse — A great tool for checking performance, best practices, accessibility, and SEO.

Client (Optional)

Offline — How will you store data on the client?

Data Virtualization — Do you have a lot of data and only want to load what’s being shown on the screen? This goes beyond paging.

Themes — Do you want to support themes and have them match the OS theme? Should you provide a more compact mode?

Printing — Will users want to print your application’s data or views?

SPA Only — Hosting model (client / server / progressive web app / hybrid / native), lazy loading, pre-rendering and AOT compilation.

Third-Party Clients — Should you integrate with third party clients like those applications found in Microsoft 365?

Did I miss something? Leave me a comment with items from your checklist.

--

--