Introduction
Service-Oriented Architecture (SOA) is a powerful approach to designing modular, scalable applications. In this post, we will develop an airline booking system using SOA in C# with .NET 9. We will incorporate modern architecture patterns, including the Resolver, Repository, and Service patterns, along with Entity Framework for data access and Data Transfer Objects (DTOs) for encapsulation. We will also implement Dependency Injection (DI) to ensure better separation of concerns and easier testing. Finally, we will bind the data to an MVVM-oriented simple UI, leveraging asynchronous programming where necessary, and making use of cancellation tokens for better request management. To ensure the UI displays meaningful data, we will include sample data generation.
Architecture Overview
The system will be composed of the following layers:
- Presentation Layer (MVVM-based UI)
- Service Layer (Business logic and orchestration)
- Repository Layer (Data access with Entity Framework Core)
- Database Layer (SQL Server using EF Core)
- Dependency Injection (Managing dependencies efficiently)
Setting Up the Project
We will create a C# .NET 9 solution with multiple projects:
AirlineBooking.Domain
– Contains entity models and DTOsAirlineBooking.Data
– Implements the repository pattern using Entity Framework CoreAirlineBooking.Service
– Implements business logicAirlineBooking.API
– Exposes services via Web APIAirlineBooking.UI
– A simple WPF UI using MVVM
1. Defining the Domain Entities and DTOs
First, let’s define the core entities and DTOs.
public class Flight
{
public int Id { get; set; }
public string FlightNumber { get; set; } = string.Empty;
public string Origin { get; set; } = string.Empty;
public string Destination { get; set; } = string.Empty;
public DateTime DepartureTime { get; set; }
public int AvailableSeats { get; set; }
}
public class FlightDto
{
public int Id { get; set; }
public string FlightNumber { get; set; } = string.Empty;
public string Origin { get; set; } = string.Empty;
public string Destination { get; set; } = string.Empty;
}
2. Implementing the Repository Pattern
The repository pattern abstracts data access logic.
public interface IFlightRepository
{
Task<IEnumerable<Flight>> GetFlightsAsync(CancellationToken cancellationToken);
}
public class FlightRepository : IFlightRepository
{
public async Task<IEnumerable<Flight>> GetFlightsAsync(CancellationToken cancellationToken)
{
await Task.Delay(500, cancellationToken); // Simulate database latency
return new List<Flight>
{
new Flight { Id = 1, FlightNumber = "LH123", Origin = "Munich", Destination = "New York", DepartureTime = DateTime.UtcNow.AddHours(5), AvailableSeats = 20 },
new Flight { Id = 2, FlightNumber = "BA456", Origin = "London", Destination = "Berlin", DepartureTime = DateTime.UtcNow.AddHours(3), AvailableSeats = 15 }
};
}
}
3. Implementing the Service Layer
public interface IFlightService
{
Task<IEnumerable<FlightDto>> GetFlightsAsync(CancellationToken cancellationToken);
}
public class FlightService : IFlightService
{
private readonly IFlightRepository _repository;
public FlightService(IFlightRepository repository)
{
_repository = repository;
}
public async Task<IEnumerable<FlightDto>> GetFlightsAsync(CancellationToken cancellationToken)
{
var flights = await _repository.GetFlightsAsync(cancellationToken);
return flights.Select(f => new FlightDto
{
Id = f.Id,
FlightNumber = f.FlightNumber,
Origin = f.Origin,
Destination = f.Destination
});
}
}
4. Implementing the MVVM-Based WPF UI
FlightViewModel
public class FlightViewModel : INotifyPropertyChanged
{
private readonly IFlightService _flightService;
private ObservableCollection<FlightDto> _flights;
public ObservableCollection<FlightDto> Flights
{
get => _flights;
set { _flights = value; OnPropertyChanged(); }
}
public ICommand LoadFlightsCommand { get; }
public FlightViewModel(IFlightService flightService)
{
_flightService = flightService;
Flights = new ObservableCollection<FlightDto>();
LoadFlightsCommand = new AsyncRelayCommand(LoadFlightsAsync);
}
private async Task LoadFlightsAsync()
{
using var cts = new CancellationTokenSource();
var flights = await _flightService.GetFlightsAsync(cts.Token);
App.Current.Dispatcher.Invoke(() =>
{
Flights.Clear();
foreach (var flight in flights)
Flights.Add(flight);
});
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null!)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
FlightView.xaml
<Window x:Class="AirlineBooking.UI.Views.FlightView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Flight Booking" Height="350" Width="600">
<Grid>
<ListView ItemsSource="{Binding Flights}" Margin="10">
<ListView.View>
<GridView>
<GridViewColumn Header="Flight No" DisplayMemberBinding="{Binding FlightNumber}"/>
<GridViewColumn Header="Origin" DisplayMemberBinding="{Binding Origin}"/>
<GridViewColumn Header="Destination" DisplayMemberBinding="{Binding Destination}"/>
</GridView>
</ListView.View>
</ListView>
<Button Content="Load Flights" Command="{Binding LoadFlightsCommand}" HorizontalAlignment="Right" Margin="10"/>
</Grid>
</Window>
Conclusion
We built an SOA-based airline booking system with a complete MVVM-based WPF UI including XAML layout. Sample data ensures the UI displays meaningful flights without requiring a full database. This structure ensures modularity, scalability, and ease of maintenance.