Building a Modern Development Platform: Tool Selection ๐ ๏ธ
- The Tool Selection Process ๐ฏ
- Cloud Environment: Azure โ๏ธ
- Local Development: .NET Aspire ๐ป
- API Contract Generation: TypeSpec ๐
- API Client Code Generation: Kiota ๐ง
- Infrastructure as Code: Terraform ๐๏ธ
- DevOps Platform: Azure DevOps โป๏ธ
- Application Templates: .NET Core Templating Engine ๐ฆ
- Developer Tooling: Custom CLI ๐ฅ๏ธ
- Security: Okta + Terraform ๐
- Service Virtualization: WireMock Cloud ๐ญ
- Application Development: Framework Flexibility ๐จ
- How It All Fits Together ๐งฉ
- Whatโs Next ๐ฎ
In our previous post, we outlined the vision for our modern development platform. Now comes the critical question: which tools would form the foundation of this transformation? The choices we made needed to work together cohesively while providing a clear migration path from our legacy .NET Framework applications.
The Tool Selection Process ๐ฏ
Our approach to tool selection was pragmatic rather than aspirational. We werenโt looking for the newest, shiniest technologies โ we needed proven tools that would:
- ๐ Work together as an integrated ecosystem
- ๐ข Support our existing Microsoft-heavy technology stack
- ๐ฃ๏ธ Provide a reasonable migration path from .NET Framework
- ๐ค Offer strong community support and long-term viability
- โ Solve real problems we were experiencing today
Letโs dive into each decision and the reasoning behind it.
Cloud Environment: Azure โ๏ธ
The Problem: We needed to move from on-premise infrastructure to the cloud, but which cloud provider?
The Solution: We chose Microsoft Azure as our cloud platform.
Why Azure? As a government agency deeply invested in the Microsoft ecosystem, Azure was the natural choice. Our existing expertise with Microsoft technologies provided a smoother learning curve than switching to AWS or GCP. More importantly, Azureโs offerings aligned perfectly with our needs:
- ๐ Azure API Management provided enterprise-grade API gateway capabilities with built-in security, rate limiting, and policy enforcement
- ๐ฅ๏ธ App Service Plans offered a managed platform for hosting our applications without the complexity of managing underlying infrastructure
- ๐ Entra ID integration simplified our authentication and authorization requirements
- ๐ค Existing Microsoft partnerships and support made adoption smoother
While AWS might offer more services, Azureโs integration with our existing Microsoft stack made it the clear winner for our use case.
Local Development: .NET Aspire ๐ป
The Problem: Local development was a nightmare. Developers needed complex environment setups, database connections, and multiple services running simultaneously just to work on a single feature.
The Solution: We adopted .NET Aspire for orchestrating local development environments.
Why Aspire? Aspire is a relatively new project from Microsoft, but itโs backed by some serious talent from the .NET team. It solves a problem we were struggling with: how do you run a distributed application locally without losing your mind?
Aspire provides:
- ๐ Service orchestration that makes it trivial to run multiple services, databases, and dependencies locally
- ๐ Service discovery built-in, so services can find each other without hardcoded configuration
- ๐ Telemetry and observability out of the box with OpenTelemetry integration
- ๐ Future potential as an Infrastructure as Code (IaC) and deployment tool โ the Aspire team is actively working on cloud deployment capabilities
What really sold us was the developer experience. With Aspire, a developer can clone a repo, hit F5, and have the entire application stack running locally with proper service-to-service communication, databases, and monitoring. No more โit works on my machineโ excuses.
The fact that Aspire is evolving to handle deployment to Azure Container Apps means our investment today will pay dividends tomorrow when we need to deploy these same applications to the cloud.
API Contract Generation: TypeSpec ๐
The Problem: Teams were building APIs with inconsistent structures, documentation lagged behind implementation, and client teams couldnโt start development until the API was mostly complete.
The Solution: We adopted TypeSpec (formerly known as Cadl) for API contract definition.
Why TypeSpec? TypeSpec represents the future of API specification. While OpenAPI (Swagger) is the industry standard, writing OpenAPI by hand is tedious and error-prone. TypeSpec provides:
- โ๏ธ Clean, readable syntax thatโs easier to write and maintain than raw OpenAPI YAML
- ๐ Type safety and validation at the specification level
- ๐ค OpenAPI emission โ TypeSpec can generate OpenAPI 3.0 specs, so we get the best of both worlds
- ๐ค Code generation capabilities for both clients and servers
- ๐ข Microsoft backing with active development and growing ecosystem
By defining our APIs in TypeSpec first, we enforce a contract-first development approach. The API contract becomes the source of truth, and both client and server implementations are generated from that contract. This eliminates the drift between documentation and implementation that plagued our legacy systems.
API Client Code Generation: Kiota ๐ง
The Problem: While TypeSpec can generate client code, we needed more flexibility, especially around authentication patterns that varied across our services.
The Solution: We chose Kiota for generating API client SDKs.
Why Kiota? Kiota is Microsoftโs next-generation API client generator that produces idiomatic code for multiple languages (C#, TypeScript, Java, Python, Go, and more). What sets Kiota apart:
- ๐ Authentication flexibility โ Kiota makes it easy to plug in different authentication providers (OAuth, API keys, custom headers)
- โก Minimal dependencies โ Generated clients are lightweight with only necessary dependencies
- ๐ฑ Native async/await patterns that feel natural in modern .NET and TypeScript
- ๐ OpenAPI 3.0 support โ Works perfectly with the OpenAPI specs generated by TypeSpec
- ๐ Multi-language support โ One spec, multiple client libraries for different teams
The combination of TypeSpec for contract definition and Kiota for client generation gives us a powerful workflow: define the API in TypeSpec, emit OpenAPI, generate clients with Kiota. We package these generated clients and publish them to our internal private NuGet feed, making it trivial for consuming teams to add a reference and start making type-safe API calls. Backend and frontend teams can work in parallel with confidence that the contract is being honored.
Infrastructure as Code: Terraform ๐๏ธ
The Problem: Manual infrastructure provisioning was slow, error-prone, and made it difficult to maintain consistency across environments.
The Solution: We standardized on HashiCorp Terraform with Terraform Cloud for state management and module hosting.
Why Terraform? While Azure has its own IaC solution (Bicep/ARM templates), Terraformโs multi-cloud support and mature ecosystem made it the better choice:
- ๐ Provider ecosystem โ Terraform has providers for everything: Azure, Okta, GitHub, DNS, monitoring tools
- ๐ฆ Reusable modules โ We can create standardized modules for common patterns (API + database, worker service, etc.)
- ๐พ State management โ Terraform Cloud handles state storage, locking, and provides a UI for visualizing infrastructure
- ๐๏ธ Plan and Apply workflow โ The ability to preview changes before applying them reduces mistakes
- โ๏ธ Multi-cloud flexibility โ If we ever need to use services from other clouds, our IaC patterns remain consistent
Terraform Cloudโs private module registry allows us to publish internal modules that teams can consume, ensuring everyone follows the same infrastructure patterns and security baselines.
DevOps Platform: Azure DevOps โป๏ธ
The Problem: We were already using Azure DevOps on-premise, but it was outdated and lacked the modern CI/CD capabilities we needed.
The Solution: We migrated to Azure DevOps Cloud and created standardized pipeline templates.
Why Azure DevOps Cloud? We considered GitHub Actions, but Azure DevOps provided better support for our specific needs:
- ๐ Enterprise features we were already using (work item tracking, test management)
- ๐ Pipeline templates allow us to create reusable, standardized build and deploy workflows
- ๐ฆ Artifact feeds for hosting our NuGet packages and npm modules
- ๐ Variable groups and service connections for managing secrets and cloud credentials
- ๐ก Existing organizational knowledge โ our teams already knew Azure DevOps
By creating pipeline templates, we ensure every application follows the same build, test, and deployment process. Developers donโt need to be CI/CD experts โ they just reference the template and provide a few parameters.
Application Templates: .NET Core Templating Engine ๐ฆ
The Problem: Each team was creating new projects from scratch, leading to inconsistent structure, missing security configurations, and duplicated effort.
The Solution: We leveraged the .NET Core templating engine to create standardized project templates.
Why .NET Templates? The .NET templating system is powerful and often overlooked:
- ๐ง Built into the .NET SDK โ No additional tools required
- ๐๏ธ Flexible parameterization โ Templates can accept parameters to customize the generated code
- ๐ Multi-file templates โ Can generate entire project structures with proper folder organization
- โ Conditional content โ Include/exclude files based on template parameters
- โ๏ธ Post-actions โ Run commands after template generation (restore packages, initialize git, etc.)
We created comprehensive templates for common scenarios that include everything needed to run on our platform:
- ๐ API with Database Backend โ .NET API with TypeSpec contract, Entity Framework, Terraform infrastructure, and Aspire orchestration
- โ๏ธ API with React Frontend โ Full-stack application with .NET backend, React TypeScript frontend, shared infrastructure, and local dev setup
- โฐ Background Worker Service โ .NET worker with infrastructure provisioning and Aspire integration
- โก Azure Function โ Serverless function with dependency injection and Terraform deployment configuration
These templates donโt just scaffold code โ they include the complete platform integration: TypeSpec contracts, Terraform modules for infrastructure, Aspire AppHost configuration for local development, proper project structure, configuration management, logging, health checks, and our security baseline. A developer can run a single command and have a fully configured application ready to develop and deploy.
Developer Tooling: Custom CLI ๐ฅ๏ธ
The Problem: Even with templates, developers needed to install multiple tools, configure their environment, and remember various commands to get started.
The Solution: We built a custom CLI tool that wraps common development tasks.
Why Build Our Own CLI? Our CLI, built in .NET Core, serves as a friendly wrapper around our platform:
- ๐จ Environment setup โ Installs and configures necessary development tools (Docker, .NET SDK, Node.js, Terraform)
- ๐ Template scaffolding โ Wraps the .NET templating engine with a user-friendly interface
- ๐ Project initialization โ Creates new projects with all necessary files, Azure DevOps pipelines, and git repositories
- ๐ค Deployment helpers โ Simplifies deploying applications to various environments
- โ Configuration validation โ Checks that developer environments are properly configured
The CLI provides a consistent interface for developers, hiding complexity while maintaining the flexibility of the underlying tools. Itโs the โeasy buttonโ for our platform.
Security: Okta + Terraform ๐
The Problem: Security configuration was manual, inconsistent, and difficult to audit. Creating new auth servers, permissions, and integrations required tickets and days of waiting.
The Solution: We automated our Okta configuration using Terraformโs Okta provider.
Why Terraform for Okta? We already had Okta as our identity provider, but managing it manually was painful:
- ๐ Okta Terraform provider allows us to define auth servers, scopes, claims, groups, and applications as code
- ๐ Version control for security configurations โ every change is tracked and reviewed
- ๐ค Automated provisioning โ Creating a new API automatically creates its auth server configuration
- ๐ Consistency โ Every API follows the same security patterns because theyโre generated from the same Terraform modules
- ๐ Audit trail โ Git history shows who changed what security configuration and when
This automation reduced auth server setup from days to minutes and eliminated configuration errors.
Service Virtualization: WireMock Cloud ๐ญ
The Problem: Testing integrations required all dependent services to be running, which was expensive in lower environments and impossible for external third-party services.
The Solution: We adopted WireMock and WireMock Cloud for service virtualization and mocking.
Why WireMock? WireMock is the industry-standard tool for API mocking:
- ๐ Contract-based mocking โ Create mocks from OpenAPI specs (generated by TypeSpec!)
- โ๏ธ WireMock Cloud โ Hosted solution from the creator of the open-source project, providing:
- ๐ Centralized mock storage
- ๐ฅ Team collaboration on mock definitions
- ๐ Environment-specific mock configurations
- ๐ Analytics on mock usage
- ๐ Stateful mocking โ Mocks can simulate complex workflows and state changes
- ๐งช Integration testing โ Developers can test against mocks locally without external dependencies
This dramatically reduced our infrastructure costs (fewer environments needed) and improved developer productivity (no more waiting for downstream services to be available).
Application Development: Framework Flexibility ๐จ
The Problem: We needed to support modern application development while not forcing every team into the same technology choices.
The Solution: We adopted a container-first, framework-agnostic approach that provides flexibility while maintaining platform standards.
Why Framework Flexibility? While many of our applications currently use .NET Core for backends and React with TypeScript for frontends, we deliberately designed the platform to support any technology stack:
- ๐ณ Container-based deployment โ As long as an application can produce a Docker container, it can run on our platform
- ๐ง Language agnostic โ Teams can choose the best tool for their specific problem domain
- ๐ Common platform patterns โ Regardless of framework choice, all applications follow the same patterns for:
- ๐ API contracts (TypeSpec/OpenAPI)
- ๐๏ธ Infrastructure provisioning (Terraform)
- ๐ฅ๏ธ Local development (Aspire orchestration)
- ๐ Authentication (Okta integration)
- ๐ Observability (OpenTelemetry)
- ๐ CI/CD (Azure DevOps pipelines)
Current Popular Choices: While teams have flexibility, most are choosing:
- โ๏ธ .NET Core for backends due to our existing expertise and migration path from .NET Framework
- โ๏ธ React with TypeScript for frontends for type safety and integration with Kiota-generated clients
- ๐ Python for data processing and ML workloads
- ๐ฆ Node.js for specific microservices where the npm ecosystem provides advantages
The key insight: standardize the platform, not the programming language. By focusing on contracts, infrastructure patterns, and observability rather than specific frameworks, we give teams the flexibility they need while maintaining consistency where it matters.
How It All Fits Together ๐งฉ
These tools form an integrated ecosystem:
- ๐จโ๐ป Developer uses the CLI to create a new API from a template
- ๐ API contract is defined in TypeSpec and committed to git
- ๐ Azure DevOps pipeline builds the API, runs tests, and deploys to Azure
- ๐๏ธ Infrastructure is provisioned via Terraform (App Service, database, API Management)
- ๐ Security is configured automatically in Okta via Terraform
- ๐ Kiota generates client SDKs for frontend teams
- ๐ฅ๏ธ Aspire orchestrates local development environment
- ๐ญ WireMock provides mocks for external dependencies
- โ๏ธ React frontend consumes the Kiota-generated client with type safety
Every tool was chosen to solve a specific problem while working harmoniously with the others. The result is a platform that accelerates development, enforces best practices, and provides a clear path forward from our legacy systems.
Whatโs Next ๐ฎ
In upcoming posts, weโll dive deep into each of these tools:
- ๐ Setting up .NET Aspire for local development
- ๐ Creating TypeSpec contracts and generating OpenAPI
- ๐ง Building Kiota clients with custom authentication
- ๐๏ธ Terraform patterns for Azure infrastructure
- ๐ฆ Creating custom .NET templates
- ๐ญ Service virtualization strategies with WireMock
Each tool deserves its own deep dive with practical examples from our real-world implementation.
Stay tuned! ๐
More
Recent Posts
- » Building a Modern Development Platform: Kiota for Multi-Language API Clients ๐ง
- » Building a Modern Development Platform: Deploying Platform Documentation with Azure Storage and Front Door ๐
- » Building a Modern Development Platform: Terraform & Terraform Cloud for Azure Infrastructure ๐๏ธ
- » Building a Modern Development Platform: TypeSpec for Contract-First API Development ๐
- » Building a Modern Development Platform: Aspire for Local Development