Skip to content

Commit 6c45ee8

Browse files
Merge pull request #155 from buituananhdev/feat/booking/dialogflow
feat: sse call after booking
2 parents cbeb420 + 96b0fa1 commit 6c45ee8

10 files changed

Lines changed: 195 additions & 52 deletions

File tree

RailwayExpressVN.ServiceDefaults/Extensions.cs

Lines changed: 32 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@
22
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
33
using Microsoft.Extensions.DependencyInjection;
44
using Microsoft.Extensions.Diagnostics.HealthChecks;
5+
using Microsoft.Extensions.Http.Resilience;
56
using Microsoft.Extensions.Logging;
6-
using Microsoft.Extensions.ServiceDiscovery;
77
using OpenTelemetry;
88
using OpenTelemetry.Metrics;
99
using OpenTelemetry.Trace;
10+
using Polly;
1011

1112
namespace Microsoft.Extensions.Hosting;
1213

13-
// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry.
14-
// This project should be referenced by each service project in your solution.
15-
// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults
1614
public static class Extensions
1715
{
1816
public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
@@ -21,22 +19,25 @@ public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder) where
2119

2220
builder.AddDefaultHealthChecks();
2321

24-
builder.Services.AddServiceDiscovery();
25-
2622
builder.Services.ConfigureHttpClientDefaults(http =>
2723
{
28-
// Turn on resilience by default
29-
http.AddStandardResilienceHandler();
24+
http.AddStandardResilienceHandler().Configure(options =>
25+
{
26+
options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(20);
3027

31-
// Turn on service discovery by default
32-
http.AddServiceDiscovery();
33-
});
28+
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(10);
3429

35-
// Uncomment the following to restrict the allowed schemes for service discovery.
36-
// builder.Services.Configure<ServiceDiscoveryOptions>(options =>
37-
// {
38-
// options.AllowedSchemes = ["https"];
39-
// });
30+
options.Retry.MaxRetryAttempts = 2;
31+
options.Retry.Delay = TimeSpan.FromMilliseconds(300);
32+
options.Retry.BackoffType = DelayBackoffType.Exponential;
33+
options.Retry.UseJitter = true;
34+
35+
options.CircuitBreaker.FailureRatio = 0.5;
36+
options.CircuitBreaker.MinimumThroughput = 5;
37+
options.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(60);
38+
options.CircuitBreaker.BreakDuration = TimeSpan.FromSeconds(30);
39+
});
40+
});
4041

4142
return builder;
4243
}
@@ -53,15 +54,15 @@ public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder) w
5354
.WithMetrics(metrics =>
5455
{
5556
metrics.AddAspNetCoreInstrumentation()
56-
.AddHttpClientInstrumentation()
57-
.AddRuntimeInstrumentation();
57+
.AddHttpClientInstrumentation()
58+
.AddRuntimeInstrumentation();
5859
})
5960
.WithTracing(tracing =>
6061
{
6162
tracing.AddSource(builder.Environment.ApplicationName)
62-
.AddAspNetCoreInstrumentation()
63-
.AddGrpcClientInstrumentation()
64-
.AddHttpClientInstrumentation();
63+
.AddAspNetCoreInstrumentation()
64+
.AddGrpcClientInstrumentation()
65+
.AddHttpClientInstrumentation();
6566
});
6667

6768
builder.AddOpenTelemetryExporters();
@@ -78,40 +79,31 @@ private static TBuilder AddOpenTelemetryExporters<TBuilder>(this TBuilder builde
7879
builder.Services.AddOpenTelemetry().UseOtlpExporter();
7980
}
8081

81-
// Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
82-
//if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
83-
//{
84-
// builder.Services.AddOpenTelemetry()
85-
// .UseAzureMonitor();
86-
//}
82+
// Azure Monitor (optional)
83+
// if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
84+
// {
85+
// builder.Services.AddOpenTelemetry().UseAzureMonitor();
86+
// }
8787

8888
return builder;
8989
}
9090

9191
public static TBuilder AddDefaultHealthChecks<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
9292
{
9393
builder.Services.AddHealthChecks()
94-
// Add a default liveness check to ensure app is responsive
9594
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
9695

9796
return builder;
9897
}
9998

10099
public static WebApplication MapDefaultEndpoints(this WebApplication app)
101100
{
102-
// Adding health checks endpoints to applications in non-development environments has security implications.
103-
// See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
104-
if (app.Environment.IsDevelopment())
105-
{
106-
// All health checks must pass for app to be considered ready to accept traffic after starting
107-
app.MapHealthChecks("/health");
101+
app.MapHealthChecks("/health");
108102

109-
// Only health checks tagged with the "live" tag must pass for app to be considered alive
110-
app.MapHealthChecks("/alive", new HealthCheckOptions
111-
{
112-
Predicate = r => r.Tags.Contains("live")
113-
});
114-
}
103+
app.MapHealthChecks("/alive", new HealthCheckOptions
104+
{
105+
Predicate = r => r.Tags.Contains("live")
106+
});
115107

116108
return app;
117109
}

src/Services/Admin/Admin.Application/Services/TrainScheduleService/TrainScheduleService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class TrainScheduleService : BaseService<TrainSchedule, AddTrainScheduleD
1919

2020
private const string TRAIN_SCHEDULES_CACHE_KEY = "train_schedules";
2121
private const string TRAIN_SCHEDULE_INFO_CACHE_KEY = "train_schedule_info";
22-
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(15);
22+
private static readonly TimeSpan CacheDuration = TimeSpan.FromHours(24);
2323
private static readonly TimeSpan InfoCacheDuration = TimeSpan.FromHours(1);
2424

2525
private static readonly List<Expression<Func<TrainSchedule, object>>> DefaultIncludes = new List<Expression<Func<TrainSchedule, object>>>

src/Services/Admin/Admin.Infrastructure/GrpcServices/AdminService.cs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,34 @@ public override async Task<GetStationInformationResponse> GetStationInformation(
3838
};
3939
}
4040

41-
public override async Task<GetTrainScheduleResponse> GetTrainSchedule(GetTrainScheduleRequest request, ServerCallContext context)
41+
public override async Task<GetTrainScheduleResponse> GetTrainSchedule(
42+
GetTrainScheduleRequest request,
43+
ServerCallContext context)
4244
{
43-
var results = await _trainScheduleService.GetTrainSchedulesAsync(_mapper.Map<GetTrainSchedulesDto>(request));
44-
var schedule = results.OrderBy(s => Math.Abs((s.DepartureTime.TimeOfDay - request.DepartureTime.ToTimeSpan()).TotalMinutes)).First();
45-
var response = new GetTrainScheduleResponse();
46-
response.TrainScheduleId = schedule.Id.ToString();
47-
response.TrainId = schedule.Train.Id.ToString();
48-
response.BasePrice = (double)schedule.FromPrice;
49-
return response;
45+
var dto = new GetTrainSchedulesDto
46+
{
47+
DepartureStationId = Guid.Parse(request.DepartureStationId),
48+
ArrivalStationId = Guid.Parse(request.ArrivalStationId),
49+
DepartureDate = request.DepartureDate.ToDateTime()
50+
};
51+
52+
var schedules = await _trainScheduleService.GetTrainSchedulesAsync(dto)
53+
.ConfigureAwait(false);
54+
55+
if (schedules.Count == 0)
56+
throw new RpcException(new Status(StatusCode.NotFound,
57+
"Không tìm thấy lịch tàu phù hợp"));
58+
59+
var target = request.DepartureTime.ToTimeSpan();
60+
var bestMatch = schedules.MinBy(s =>
61+
Math.Abs((s.DepartureTime.TimeOfDay - target).TotalMinutes));
62+
63+
return new GetTrainScheduleResponse
64+
{
65+
TrainScheduleId = bestMatch.Id.ToString(),
66+
TrainId = bestMatch.Train.Id.ToString(),
67+
BasePrice = (double)bestMatch.FromPrice
68+
};
5069
}
5170

5271
public override async Task<GetRandomeAvailableSeatResponse> GetRandomeAvailableSeat(GetRandomeAvailableSeatRequest request, ServerCallContext context)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Booking.Application.Interfaces;
2+
using Microsoft.AspNetCore.Http;
3+
using Microsoft.AspNetCore.Mvc;
4+
5+
namespace Booking.API.Controllers;
6+
[Route("api/[controller]")]
7+
[ApiController]
8+
public class SSEController : ControllerBase
9+
{
10+
private readonly ISSEPublisher _publisher;
11+
12+
public SSEController(ISSEPublisher publisher) => _publisher = publisher;
13+
14+
[HttpGet("{sessionId}")]
15+
public async Task Get(string sessionId)
16+
{
17+
await _publisher.RegisterClientAsync(sessionId, HttpContext);
18+
}
19+
}

src/Services/Booking/Booking.Application/Booking.Application.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1818
</PackageReference>
1919
<PackageReference Include="MassTransit" Version="8.4.0" />
20+
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
2021
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
2122
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
2223
<PackageReference Include="Nanoid" Version="3.1.0" />
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Microsoft.AspNetCore.Http;
2+
3+
namespace Booking.Application.Interfaces;
4+
public interface ISSEPublisher
5+
{
6+
Task RegisterClientAsync(string sessionId, HttpContext context);
7+
8+
Task SendAsync(string sessionId, string eventName, object data);
9+
}

src/Services/Booking/Booking.Application/Services/DialogflowService/DialogflowService.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Common.Protos;
99
using System.Diagnostics;
1010
using System.Net.Sockets;
11+
using Booking.Application.Interfaces;
1112

1213
namespace Booking.Application.Services
1314
{
@@ -20,7 +21,7 @@ public class DialogflowService : IDialogflowService
2021
private readonly string _projectId;
2122
private readonly string _languageCode;
2223
private readonly PaymentGrpcService.PaymentGrpcServiceClient _paymentGrpcServiceClient;
23-
24+
private readonly ISSEPublisher _ssePublisher;
2425
private static class Messages
2526
{
2627
public const string MissingFieldsTemplate = "Vui lòng cung cấp thêm: {0}";
@@ -65,12 +66,14 @@ public DialogflowService(
6566
IPassengerInfoService passengerInfoService,
6667
IConfiguration configuration,
6768
ILogger<DialogflowService> logger,
68-
PaymentGrpcService.PaymentGrpcServiceClient paymentGrpcServiceClient)
69+
PaymentGrpcService.PaymentGrpcServiceClient paymentGrpcServiceClient,
70+
ISSEPublisher ssePublisher)
6971
{
7072
_ticketService = ticketService ?? throw new ArgumentNullException(nameof(ticketService));
7173
_passengerInfoService = passengerInfoService ?? throw new ArgumentNullException(nameof(passengerInfoService));
7274
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
7375
_paymentGrpcServiceClient = paymentGrpcServiceClient;
76+
_ssePublisher = ssePublisher;
7477
_projectId = configuration["Dialogflow:ProjectId"]
7578
?? throw new InvalidOperationException("Dialogflow:ProjectId not found in configuration.");
7679

@@ -167,6 +170,12 @@ public async Task<DialogflowResponse> HandleBookingTicket(Dictionary<string, obj
167170
_logger.LogInformation("CreatePayment took {Elapsed} ms", sw.ElapsedMilliseconds);
168171
sw.Restart();
169172

173+
if (parameters.TryGetValue("sessionId", out var sidObj) && sidObj is string sessionId && !string.IsNullOrWhiteSpace(sessionId))
174+
{
175+
await _ssePublisher.SendAsync(sessionId, "paymentUrl", new { PaymentUrl = response.PaymentUrl });
176+
_logger.LogInformation("Sent PaymentUrl SSE to session {SessionId}", sessionId);
177+
}
178+
170179
return new DialogflowResponse
171180
{
172181
FulfillmentText = Messages.BookingInProgressMessage,

src/Services/Booking/Booking.Application/Services/TicketService/TicketService.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using Microsoft.Extensions.Logging;
1717
using Newtonsoft.Json;
1818
using NanoidDotNet;
19+
using System.Diagnostics;
1920

2021
namespace Booking.Application.Services
2122
{
@@ -385,6 +386,7 @@ private async Task<TicketDto> GetTicketAsync(Specification<Ticket> specification
385386

386387
public async Task<TicketDto> CreateTicketForDialogfowAsync(DialogflowCreateTicketRequest request)
387388
{
389+
var sw = Stopwatch.StartNew();
388390
const string cacheKey = "station:all";
389391
var cached = await _cacheService.GetCacheAsync<List<Booking.Application.Dtos.Station>>(cacheKey);
390392

@@ -420,6 +422,8 @@ public async Task<TicketDto> CreateTicketForDialogfowAsync(DialogflowCreateTicke
420422
new GetStationInformationRequest { StationName = request.ArrivalStation });
421423
arrivalStationId = grpcResp.StationId;
422424
}
425+
_logger.LogInformation("GetStationInformation took {Elapsed} ms", sw.ElapsedMilliseconds);
426+
sw.Restart();
423427

424428
var scheduleRequest = new GetTrainScheduleRequest
425429
{
@@ -431,13 +435,18 @@ public async Task<TicketDto> CreateTicketForDialogfowAsync(DialogflowCreateTicke
431435

432436
var schedule = await _adminGrpcServiceClient.GetTrainScheduleAsync(scheduleRequest);
433437

438+
_logger.LogInformation("GetTrainScheduleAsync took {Elapsed} ms", sw.ElapsedMilliseconds);
439+
sw.Restart();
440+
434441
var seatIds = await _adminGrpcServiceClient.GetRandomeAvailableSeatAsync(new GetRandomeAvailableSeatRequest
435442
{
436443
TrainId = schedule.TrainId,
437444
ScheduleId = schedule.TrainScheduleId,
438445
JourneyDate = Timestamp.FromDateTime(request.Date.ToUniversalTime()),
439446
Quantity = request.Quantity
440447
});
448+
_logger.LogInformation("GetRandomeAvailableSeatAsync took {Elapsed} ms", sw.ElapsedMilliseconds);
449+
sw.Restart();
441450

442451
var createDto = new AddTicketDto
443452
{

src/Services/Booking/Booking.Infrastructure/DependencyInjection.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
using Booking.Application.Repositories;
1+
using Booking.Application.Interfaces;
2+
using Booking.Application.Repositories;
23
using Booking.Infrastructure.Consumers;
34
using Booking.Infrastructure.Repositories;
5+
using Booking.Infrastructure.SSE;
46
using Common.Application.Repositories;
57
using Common.Infrastructure;
68
using Common.Infrastructure.Repositories;
@@ -35,6 +37,8 @@ public static IServiceCollection AddInfrastructure(this IServiceCollection servi
3537
services.AddScoped<ITicketSeatRepository, TicketSeatRepository>();
3638
services.AddScoped<IBookingOrderRepository, BookingOrderRepository>();
3739

40+
services.AddSingleton<ISSEPublisher, SSEPublisher>();
41+
3842
services.AddGrpc(options =>
3943
{
4044
options.EnableDetailedErrors = true;

0 commit comments

Comments
 (0)