Skip to content

Commit

Permalink
Implement Strongly Typed Ids (#33)
Browse files Browse the repository at this point in the history
Co-authored-by: NikolaVetnic <[email protected]>
  • Loading branch information
HunorTotBagi and NikolaVetnic authored Dec 30, 2024
1 parent a0606f4 commit 1ab6f64
Show file tree
Hide file tree
Showing 12 changed files with 86 additions and 97 deletions.
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
global using MediatR;
global using BuildingBlocks.Domain.Exceptions;
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
using BuildingBlocks.Domain.Exceptions;
using System.Text.Json.Serialization;

namespace BuildingBlocks.Domain.ValueObjects.Ids;

public record FileAssetId
[JsonConverter(typeof(FileAssetIdJsonConverter))]
public record FileAssetId : StronglyTypedId
{
private FileAssetId(Guid value)
{
Value = value;
}
private FileAssetId(Guid value) : base(value) { }

public Guid Value { get; }
public static FileAssetId Of(Guid value) => new(value);

public static FileAssetId Of(Guid value)
{
if (value == Guid.Empty) throw new EmptyIdException("FileAssetId cannot be empty.");

return new FileAssetId(value);
}
private class FileAssetIdJsonConverter : StronglyTypedIdJsonConverter<FileAssetId>;
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,13 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;

namespace BuildingBlocks.Domain.ValueObjects.Ids;

[JsonConverter(typeof(NodeIdJsonConverter))]
public record NodeId
public record NodeId : StronglyTypedId
{
private NodeId(Guid value) => Value = value;
private NodeId(Guid value) : base(value) { }

public Guid Value { get; }
public static NodeId Of(Guid value) => new(value);

public static NodeId Of(Guid value)
{
if (value == Guid.Empty)
throw new EmptyIdException("NodeId cannot be empty.");

return new NodeId(value);
}

public override string ToString() => Value.ToString();
}

public class NodeIdJsonConverter : JsonConverter<NodeId>
{
public override NodeId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var value = reader.GetString();

if (Guid.TryParse(value, out var guid))
return NodeId.Of(guid);

throw new JsonException($"Invalid GUID format for NodeId: {value}");
}

public override void Write(Utf8JsonWriter writer, NodeId value, JsonSerializerOptions options) =>
writer.WriteStringValue(value.Value.ToString());
private class NodeIdJsonConverter : StronglyTypedIdJsonConverter<NodeId>;
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
using BuildingBlocks.Domain.Exceptions;
using System.Text.Json.Serialization;

namespace BuildingBlocks.Domain.ValueObjects.Ids;

public record NoteId
[JsonConverter(typeof(NoteIdJsonConverter))]
public record NoteId : StronglyTypedId
{
private NoteId(Guid value)
{
Value = value;
}
private NoteId(Guid value) : base(value) { }

public Guid Value { get; }
public static NoteId Of(Guid value) => new(value);

public static NoteId Of(Guid value)
{
if (value == Guid.Empty) throw new EmptyIdException("NoteId cannot be empty.");

return new NoteId(value);
}
private class NoteIdJsonConverter : StronglyTypedIdJsonConverter<NoteId>;
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
using BuildingBlocks.Domain.Exceptions;
using System.Text.Json.Serialization;

namespace BuildingBlocks.Domain.ValueObjects.Ids;

public record ReminderId
[JsonConverter(typeof(ReminderIdJsonConverter))]
public record ReminderId : StronglyTypedId
{
private ReminderId(Guid value)
{
Value = value;
}
private ReminderId(Guid value) : base(value) { }

public Guid Value { get; }
public static ReminderId Of(Guid value) => new(value);

public static ReminderId Of(Guid value)
{
if (value == Guid.Empty) throw new EmptyIdException("ReminderId cannot be empty.");

return new ReminderId(value);
}
private class ReminderIdJsonConverter : StronglyTypedIdJsonConverter<ReminderId>;
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
using BuildingBlocks.Domain.Exceptions;
using System.Text.Json.Serialization;

namespace BuildingBlocks.Domain.ValueObjects.Ids;

public record TimelineId
[JsonConverter(typeof(TimelineIdJsonConverter))]
public record TimelineId : StronglyTypedId
{
private TimelineId(Guid value)
{
Value = value;
}
private TimelineId(Guid value) : base(value) { }

public Guid Value { get; }
public static TimelineId Of(Guid value) => new(value);

public static TimelineId Of(Guid value)
{
if (value == Guid.Empty) throw new EmptyIdException("TimelineId cannot be empty.");

return new TimelineId(value);
}
private class TimelineIdJsonConverter : StronglyTypedIdJsonConverter<TimelineId>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Text.Json.Serialization;
using System.Text.Json;

namespace BuildingBlocks.Domain.ValueObjects;

public abstract record StronglyTypedId
{
protected StronglyTypedId(Guid value)
{
if (value == Guid.Empty)
throw new ArgumentException($"{GetType().Name} cannot be empty.", nameof(value));

Value = value;
}

public Guid Value { get; }

public override string ToString() => Value.ToString();
}

public class StronglyTypedIdJsonConverter<T> : JsonConverter<T> where T : StronglyTypedId
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var value = reader.GetString();

if (Guid.TryParse(value, out var guid))
{
var constructor = typeof(T).GetConstructor(new[] { typeof(Guid) });

if (constructor != null)
return (T)constructor.Invoke(new object[] { guid });
}

throw new JsonException($"Invalid GUID format for {typeof(T).Name}: {value}");
}

public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) =>
writer.WriteStringValue(value.Value.ToString());
}
14 changes: 5 additions & 9 deletions Backend/src/Modules/Files/Files.Api/Endpoints/CreateFileAsset.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
using BuildingBlocks.Domain.ValueObjects.Ids;
using Carter;
using Files.Application.Dtos;
using Files.Application.Files.Commands.CreateFileAsset;
using Mapster;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;

// ReSharper disable ClassNeverInstantiated.Global

namespace Files.Api.Endpoints;

Expand All @@ -20,7 +15,7 @@ public void AddRoutes(IEndpointRouteBuilder app)
var result = await sender.Send(command);
var response = result.Adapt<CreateFileAssetResponse>();

return Results.Created($"/Files/{response.AssetId}", response);
return Results.Created($"/Files/{response.Id}", response);
})
.WithName("CreateFileAsset")
.Produces<CreateFileAssetResponse>(StatusCodes.Status201Created)
Expand All @@ -30,6 +25,7 @@ public void AddRoutes(IEndpointRouteBuilder app)
}
}

// ReSharper disable once NotAccessedPositionalProperty.Global
public record CreateFileAssetRequest(FileAssetDto FileAsset);

public record CreateFileAssetResponse(FileAssetId AssetId);
public record CreateFileAssetResponse(FileAssetId Id);
7 changes: 7 additions & 0 deletions Backend/src/Modules/Files/Files.Api/GlobalUsing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
global using Carter;
global using Mapster;
global using MediatR;
global using Microsoft.AspNetCore.Builder;
global using Microsoft.AspNetCore.Http;
global using Microsoft.AspNetCore.Routing;
global using Files.Application.Dtos;
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Files.Application.Files.Commands.CreateFileAsset;

public record CreateFileAssetCommand(FileAssetDto FileAsset) : ICommand<CreateFileAssetResult>;

public record CreateFileAssetResult(FileAssetId AssetId);
public record CreateFileAssetResult(FileAssetId Id);

public class CreateFileCommandValidator : AbstractValidator<CreateFileAssetCommand>
{
Expand Down
4 changes: 2 additions & 2 deletions Backend/src/Modules/Files/Files.Domain/Models/FileAsset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ public class FileAsset : Aggregate<FileAssetId>

#region File

public static FileAsset Create(FileAssetId fileAssetId, string name, float size, string type, string owner, string description, List<string> sharedWith)
public static FileAsset Create(FileAssetId id, string name, float size, string type, string owner, string description, List<string> sharedWith)
{
var file = new FileAsset
{
Id = fileAssetId,
Id = id,
Name = name,
Size = size,
Type = type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ private PhaseId(Guid value)

public static PhaseId Of(Guid value)
{
ArgumentNullException.ThrowIfNull(value);
if (value == Guid.Empty) throw new EmptyIdException("PhaseId cannot be empty.");
if (value == Guid.Empty)
throw new EmptyIdException("PhaseId cannot be empty.");

return new PhaseId(value);
}
Expand Down

0 comments on commit 1ab6f64

Please sign in to comment.