Просмотр исходного кода

Migrating all queries and commands to the new handler pattern

Lukas Angerer 3 лет назад
Родитель
Сommit
2d9a58eec4

+ 14 - 9
src/RunnersMeet.Server/Controllers/TracksController.cs

@@ -15,20 +15,17 @@ public class TracksController : ControllerBase
 {
 {
 	private readonly IFileStorage _fileStorage;
 	private readonly IFileStorage _fileStorage;
 	private readonly GpxParser _gpxParser;
 	private readonly GpxParser _gpxParser;
-	private readonly QueryFactory _queryFactory;
 	private readonly IRequestRouter _requestRouter;
 	private readonly IRequestRouter _requestRouter;
 	private readonly ApiSettings _settings;
 	private readonly ApiSettings _settings;
 
 
 	public TracksController(
 	public TracksController(
 		IFileStorage fileStorage,
 		IFileStorage fileStorage,
 		GpxParser gpxParser,
 		GpxParser gpxParser,
-		QueryFactory queryFactory,
 		IOptions<ApiSettings> apiOptions,
 		IOptions<ApiSettings> apiOptions,
 		IRequestRouter requestRouter)
 		IRequestRouter requestRouter)
 	{
 	{
 		_fileStorage = fileStorage;
 		_fileStorage = fileStorage;
 		_gpxParser = gpxParser;
 		_gpxParser = gpxParser;
-		_queryFactory = queryFactory;
 		_requestRouter = requestRouter;
 		_requestRouter = requestRouter;
 		_settings = apiOptions.Value;
 		_settings = apiOptions.Value;
 	}
 	}
@@ -62,8 +59,12 @@ public class TracksController : ControllerBase
 		try
 		try
 		{
 		{
 			var gpxSummary = _gpxParser.ExtractSummary(_fileStorage.OpenFileRead(fileName));
 			var gpxSummary = _gpxParser.ExtractSummary(_fileStorage.OpenFileRead(fileName));
-			var user = _queryFactory.GetUserQuery().Get(ApiUser.Current.UserId);
-			var track = _queryFactory.CreateTrackCommand().Create(user, fileName, gpxSummary);
+			var user = _requestRouter.For(ApiUser.Current.UserId).Process<UserProfile>();
+
+			var track = _requestRouter
+				.For(new CreateTrackRequest(user, fileName, gpxSummary))
+				.Process<Track>();
+
 			return Ok(track);
 			return Ok(track);
 		}
 		}
 		catch (Exception e)
 		catch (Exception e)
@@ -76,7 +77,7 @@ public class TracksController : ControllerBase
 	[HttpGet("{id}")]
 	[HttpGet("{id}")]
 	public ActionResult<Track> GetTrack(string id)
 	public ActionResult<Track> GetTrack(string id)
 	{
 	{
-		var track = _queryFactory.TrackQuery().Get(new ObjectId(id));
+		var track = _requestRouter.For(new ObjectId(id)).Process<Track>();
 
 
 		return Ok(track);
 		return Ok(track);
 	}
 	}
@@ -88,7 +89,8 @@ public class TracksController : ControllerBase
 		{
 		{
 			throw new ArgumentException("Object ID in URL does not match track ID");
 			throw new ArgumentException("Object ID in URL does not match track ID");
 		}
 		}
-		var result = _queryFactory.UpdateTrackCommand().Update(ApiUser.Current.UserId, track);
+
+		var result = _requestRouter.For(new TrackUpdate(ApiUser.Current.UserId, track)).Process<Track>();
 
 
 		return Ok(result);
 		return Ok(result);
 	}
 	}
@@ -96,7 +98,10 @@ public class TracksController : ControllerBase
 	[HttpDelete("{id}")]
 	[HttpDelete("{id}")]
 	public ActionResult DeleteTrack(string id)
 	public ActionResult DeleteTrack(string id)
 	{
 	{
-		var fileName = _queryFactory.DeleteTrackCommand().Delete(ApiUser.Current.UserId, new ObjectId(id));
+
+		var fileName = _requestRouter
+			.For(new TrackReference(ApiUser.Current.UserId, new ObjectId(id)))
+			.Process<FileName>();
 		_fileStorage.DeleteFile(fileName);
 		_fileStorage.DeleteFile(fileName);
 
 
 		return Ok();
 		return Ok();
@@ -106,7 +111,7 @@ public class TracksController : ControllerBase
 	[AllowAnonymous]
 	[AllowAnonymous]
 	public ActionResult DownloadGpx(string hash)
 	public ActionResult DownloadGpx(string hash)
 	{
 	{
-		var track = _queryFactory.TrackQuery().GetByFileHash(hash);
+		var track = _requestRouter.For(hash).Process<Track>();
 		return _fileStorage.FileDownload(track);
 		return _fileStorage.FileDownload(track);
 	}
 	}
 }
 }

+ 6 - 4
src/RunnersMeet.Server/Controllers/UsersController.cs

@@ -10,11 +10,11 @@ namespace RunnersMeet.Server.Controllers;
 [Authorize]
 [Authorize]
 public class UsersController : ControllerBase
 public class UsersController : ControllerBase
 {
 {
-	private readonly QueryFactory _queryFactory;
+	private readonly IRequestRouter _requestRouter;
 
 
-	public UsersController(QueryFactory queryFactory)
+	public UsersController(IRequestRouter requestRouter)
 	{
 	{
-		_queryFactory = queryFactory;
+		_requestRouter = requestRouter;
 	}
 	}
 
 
 	[HttpGet("validate")]
 	[HttpGet("validate")]
@@ -25,7 +25,9 @@ public class UsersController : ControllerBase
 			throw new ApiException("UsersController.Validate call without a User / authentication token");
 			throw new ApiException("UsersController.Validate call without a User / authentication token");
 		}
 		}
 
 
-		var userProfile = _queryFactory.ValidateUserCommand().Validate(ApiUser.Current.UserId, nickname);
+		var userProfile = _requestRouter
+			.For(new ValidateUserRequest(ApiUser.Current.UserId, nickname))
+			.Process<UserProfile>();
 
 
 		return new UserValidationResult
 		return new UserValidationResult
 		{
 		{

+ 1 - 1
src/RunnersMeet.Server/Persistence/QueryPagingConfig.cs → src/RunnersMeet.Server/Domain/QueryPagingConfig.cs

@@ -1,3 +1,3 @@
-namespace RunnersMeet.Server.Persistence;
+namespace RunnersMeet.Server.Domain;
 
 
 public sealed record QueryPagingConfig(int PageSize = 10);
 public sealed record QueryPagingConfig(int PageSize = 10);

+ 5 - 0
src/RunnersMeet.Server/Domain/TrackReference.cs

@@ -0,0 +1,5 @@
+using LiteDB;
+
+namespace RunnersMeet.Server.Domain;
+
+public sealed record TrackReference(string Owner, ObjectId TrackId);

+ 3 - 0
src/RunnersMeet.Server/Domain/TrackUpdate.cs

@@ -0,0 +1,3 @@
+namespace RunnersMeet.Server.Domain;
+
+public sealed record TrackUpdate(string Owner, Track Track);

+ 11 - 9
src/RunnersMeet.Server/Persistence/CreateTrackCommand.cs

@@ -3,7 +3,9 @@ using RunnersMeet.Server.GpxFormat;
 
 
 namespace RunnersMeet.Server.Persistence;
 namespace RunnersMeet.Server.Persistence;
 
 
-public class CreateTrackCommand
+public sealed record CreateTrackRequest(UserProfile Owner, FileName FileName, GpxSummary GpxSummary);
+
+public class CreateTrackCommand : IRequestHandler<CreateTrackRequest, Track>
 {
 {
 	private readonly IDatabase _database;
 	private readonly IDatabase _database;
 
 
@@ -12,17 +14,17 @@ public class CreateTrackCommand
 		_database = database;
 		_database = database;
 	}
 	}
 
 
-	public Track Create(UserProfile owner, FileName fileName, GpxSummary gpxSummary)
+	public Track Handle(CreateTrackRequest request)
 	{
 	{
 		var track = new Track
 		var track = new Track
 		{
 		{
-			Owner = owner,
-			OwnerId = owner.UserId,
-			FileHash = fileName.Hash,
-			DisplayName = gpxSummary.TrackName,
-			Distance = gpxSummary.Distance,
-			ElevationUp = gpxSummary.ElevationUp,
-			ElevationDown = gpxSummary.ElevationDown,
+			Owner = request.Owner,
+			OwnerId = request.Owner.UserId,
+			FileHash = request.FileName.Hash,
+			DisplayName = request.GpxSummary.TrackName,
+			Distance = request.GpxSummary.Distance,
+			ElevationUp = request.GpxSummary.ElevationUp,
+			ElevationDown = request.GpxSummary.ElevationDown,
 		};
 		};
 
 
 		_database.Tracks.Insert(track);
 		_database.Tracks.Insert(track);

+ 7 - 6
src/RunnersMeet.Server/Persistence/DeleteTrackCommand.cs

@@ -1,9 +1,10 @@
 using System.Security;
 using System.Security;
 using LiteDB;
 using LiteDB;
+using RunnersMeet.Server.Domain;
 
 
 namespace RunnersMeet.Server.Persistence;
 namespace RunnersMeet.Server.Persistence;
 
 
-public class DeleteTrackCommand
+public class DeleteTrackCommand : IRequestHandler<TrackReference, FileName>
 {
 {
 	private readonly IDatabase _database;
 	private readonly IDatabase _database;
 
 
@@ -12,21 +13,21 @@ public class DeleteTrackCommand
 		_database = database;
 		_database = database;
 	}
 	}
 
 
-	public FileName Delete(string owner, ObjectId trackId)
+	public FileName Handle(TrackReference request)
 	{
 	{
-		var dbTrack = _database.Tracks.FindById(trackId);
+		var dbTrack = _database.Tracks.FindById(request.TrackId);
 
 
 		if (dbTrack == null)
 		if (dbTrack == null)
 		{
 		{
-			throw new ArgumentException($"Track with OID {trackId} does not exist", nameof(trackId));
+			throw new ArgumentException($"Track with OID {request.TrackId} does not exist", nameof(request.TrackId));
 		}
 		}
 
 
-		if (dbTrack.Owner.UserId != owner)
+		if (dbTrack.Owner.UserId != request.Owner)
 		{
 		{
 			throw new SecurityException("Trying to delete a track owned by somebody else");
 			throw new SecurityException("Trying to delete a track owned by somebody else");
 		}
 		}
 
 
-		_database.Tracks.Delete(trackId);
+		_database.Tracks.Delete(request.TrackId);
 		return FileName.FromTrack(dbTrack);
 		return FileName.FromTrack(dbTrack);
 	}
 	}
 }
 }

+ 2 - 2
src/RunnersMeet.Server/Persistence/GetUserQuery.cs

@@ -2,7 +2,7 @@ using RunnersMeet.Server.Domain;
 
 
 namespace RunnersMeet.Server.Persistence;
 namespace RunnersMeet.Server.Persistence;
 
 
-public class GetUserQuery
+public class GetUserQuery : IRequestHandler<string, UserProfile>
 {
 {
 	private readonly IDatabase _database;
 	private readonly IDatabase _database;
 
 
@@ -11,7 +11,7 @@ public class GetUserQuery
 		_database = database;
 		_database = database;
 	}
 	}
 
 
-	public UserProfile Get(string userId)
+	public UserProfile Handle(string userId)
 	{
 	{
 		var user = _database.Users.FindOne(user => user.UserId == userId);
 		var user = _database.Users.FindOne(user => user.UserId == userId);
 		if (user == null)
 		if (user == null)

+ 0 - 8
src/RunnersMeet.Server/Persistence/ITracksQuery.cs

@@ -1,8 +0,0 @@
-using RunnersMeet.Server.Domain;
-
-namespace RunnersMeet.Server.Persistence;
-
-public interface ITracksQuery
-{
-	IList<Track> Get();
-}

+ 0 - 2
src/RunnersMeet.Server/Persistence/PersistenceModule.cs

@@ -9,8 +9,6 @@ public class PersistenceModule : IAppConfigurationModule
 
 
 		services.AddSingleton<IDatabase, Database>();
 		services.AddSingleton<IDatabase, Database>();
 		services.AddSingleton<IFileStorage, FileStorage>();
 		services.AddSingleton<IFileStorage, FileStorage>();
-
-		services.AddScoped<QueryFactory, QueryFactory>();
 	}
 	}
 
 
 	public void ConfigureApplication(WebApplication app)
 	public void ConfigureApplication(WebApplication app)

+ 0 - 41
src/RunnersMeet.Server/Persistence/QueryFactory.cs

@@ -1,41 +0,0 @@
-namespace RunnersMeet.Server.Persistence;
-
-public class QueryFactory
-{
-	private readonly IDatabase _database;
-
-	public QueryFactory(IDatabase database)
-	{
-		_database = database;
-	}
-
-	public CreateTrackCommand CreateTrackCommand()
-	{
-		return new CreateTrackCommand(_database);
-	}
-
-	public TrackQuery TrackQuery()
-	{
-		return new TrackQuery(_database);
-	}
-
-	public UpdateTrackCommand UpdateTrackCommand()
-	{
-		return new UpdateTrackCommand(_database);
-	}
-
-	public DeleteTrackCommand DeleteTrackCommand()
-	{
-		return new DeleteTrackCommand(_database);
-	}
-
-	public ValidateUserCommand ValidateUserCommand()
-	{
-		return new ValidateUserCommand(_database);
-	}
-
-	public GetUserQuery GetUserQuery()
-	{
-		return new GetUserQuery(_database);
-	}
-}

+ 3 - 3
src/RunnersMeet.Server/Persistence/TrackQuery.cs

@@ -3,7 +3,7 @@ using RunnersMeet.Server.Domain;
 
 
 namespace RunnersMeet.Server.Persistence;
 namespace RunnersMeet.Server.Persistence;
 
 
-public class TrackQuery
+public class TrackQuery : IRequestHandler<ObjectId, Track>, IRequestHandler<string, Track>
 {
 {
 	private readonly IDatabase _database;
 	private readonly IDatabase _database;
 
 
@@ -12,12 +12,12 @@ public class TrackQuery
 		_database = database;
 		_database = database;
 	}
 	}
 
 
-	public Track Get(ObjectId trackId)
+	public Track Handle(ObjectId trackId)
 	{
 	{
 		return _database.Tracks.FindById(trackId);
 		return _database.Tracks.FindById(trackId);
 	}
 	}
 
 
-	public Track GetByFileHash(string fileHash)
+	public Track Handle(string fileHash)
 	{
 	{
 		return _database.Tracks.FindOne(track => track.FileHash == fileHash);
 		return _database.Tracks.FindOne(track => track.FileHash == fileHash);
 	}
 	}

+ 2 - 1
src/RunnersMeet.Server/Persistence/TracksQuery.cs

@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
 using RunnersMeet.Server.Domain;
 using RunnersMeet.Server.Domain;
 
 
 namespace RunnersMeet.Server.Persistence;
 namespace RunnersMeet.Server.Persistence;
@@ -5,7 +6,7 @@ namespace RunnersMeet.Server.Persistence;
 public class TracksQuery : IRequestHandler<TracksRequest, QueryPagingConfig, IList<Track>>
 public class TracksQuery : IRequestHandler<TracksRequest, QueryPagingConfig, IList<Track>>
 {
 {
 	private readonly IDatabase _database;
 	private readonly IDatabase _database;
-	private QueryPagingConfig _config;
+	private QueryPagingConfig _config = new QueryPagingConfig();
 
 
 	public TracksQuery(IDatabase database)
 	public TracksQuery(IDatabase database)
 	{
 	{

+ 6 - 6
src/RunnersMeet.Server/Persistence/UpdateTrackCommand.cs

@@ -3,7 +3,7 @@ using RunnersMeet.Server.Domain;
 
 
 namespace RunnersMeet.Server.Persistence;
 namespace RunnersMeet.Server.Persistence;
 
 
-public class UpdateTrackCommand
+public class UpdateTrackCommand : IRequestHandler<TrackUpdate, Track>
 {
 {
 	private readonly IDatabase _database;
 	private readonly IDatabase _database;
 
 
@@ -12,21 +12,21 @@ public class UpdateTrackCommand
 		_database = database;
 		_database = database;
 	}
 	}
 
 
-	public Track Update(string owner, Track track)
+	public Track Handle(TrackUpdate request)
 	{
 	{
-		var dbTrack = _database.Tracks.FindById(track.TrackId);
+		var dbTrack = _database.Tracks.FindById(request.Track.TrackId);
 
 
 		if (dbTrack == null)
 		if (dbTrack == null)
 		{
 		{
-			throw new ArgumentException($"Track with OID {track.TrackId} does not exist", nameof(track));
+			throw new ArgumentException($"Track with OID {request.Track.TrackId} does not exist", nameof(request.Track));
 		}
 		}
 
 
-		if (dbTrack.Owner.UserId != owner)
+		if (dbTrack.Owner.UserId != request.Owner)
 		{
 		{
 			throw new SecurityException("Trying to modify a track owned by somebody else");
 			throw new SecurityException("Trying to modify a track owned by somebody else");
 		}
 		}
 
 
-		dbTrack.DisplayName = track.DisplayName;
+		dbTrack.DisplayName = request.Track.DisplayName;
 
 
 		_database.Tracks.Update(dbTrack);
 		_database.Tracks.Update(dbTrack);
 
 

+ 7 - 5
src/RunnersMeet.Server/Persistence/ValidateUserCommand.cs

@@ -2,7 +2,9 @@ using RunnersMeet.Server.Domain;
 
 
 namespace RunnersMeet.Server.Persistence;
 namespace RunnersMeet.Server.Persistence;
 
 
-public class ValidateUserCommand
+public sealed record ValidateUserRequest(string UserId, string? DisplayName);
+
+public class ValidateUserCommand : IRequestHandler<ValidateUserRequest, UserProfile>
 {
 {
 	private readonly IDatabase _database;
 	private readonly IDatabase _database;
 
 
@@ -11,15 +13,15 @@ public class ValidateUserCommand
 		_database = database;
 		_database = database;
 	}
 	}
 
 
-	public UserProfile Validate(string userId, string? displayName)
+	public UserProfile Handle(ValidateUserRequest request)
 	{
 	{
-		var user = _database.Users.FindOne(user => user.UserId == userId);
+		var user = _database.Users.FindOne(user => user.UserId == request.UserId);
 		if (user == null)
 		if (user == null)
 		{
 		{
 			user = new UserProfile
 			user = new UserProfile
 			{
 			{
-				UserId = userId,
-				DisplayName = displayName ?? userId,
+				UserId = request.UserId,
+				DisplayName = request.DisplayName ?? request.UserId,
 			};
 			};
 			_database.Users.Insert(user);
 			_database.Users.Insert(user);
 		}
 		}

+ 5 - 2
src/RunnersMeet.Server/RequestHandlerModule.cs

@@ -27,7 +27,10 @@ public class RequestHandlerModule : IAppConfigurationModule
 			var matcher = matchers.FirstOrDefault(m => m.IsMatch(candidate));
 			var matcher = matchers.FirstOrDefault(m => m.IsMatch(candidate));
 			if (matcher != null)
 			if (matcher != null)
 			{
 			{
-				services.AddScoped(matcher.GetHandlerInterface(candidate), candidate);
+				foreach (var handlerInterface in matcher.GetHandlerInterfaces(candidate))
+				{
+					services.AddScoped(handlerInterface, candidate);
+				}
 			}
 			}
 		}
 		}
 	}
 	}
@@ -43,7 +46,7 @@ public class RequestHandlerModule : IAppConfigurationModule
 
 
 		public bool IsMatch(Type t) => t.GetInterfaces().Any(i => ImplementsExactly(i, _genericInterfaceType));
 		public bool IsMatch(Type t) => t.GetInterfaces().Any(i => ImplementsExactly(i, _genericInterfaceType));
 
 
-		public Type GetHandlerInterface(Type t) => t.GetInterfaces().Single(i => ImplementsExactly(i, _genericInterfaceType));
+		public IEnumerable<Type> GetHandlerInterfaces(Type t) => t.GetInterfaces().Where(i => ImplementsExactly(i, _genericInterfaceType));
 
 
 		private static bool ImplementsExactly(Type candidate, Type genericInterface)
 		private static bool ImplementsExactly(Type candidate, Type genericInterface)
 		{
 		{