Skip to content

Commit

Permalink
Adding Primary Constructor support
Browse files Browse the repository at this point in the history
  • Loading branch information
ardalis committed Nov 3, 2023
1 parent 8370dc6 commit 631e5ac
Show file tree
Hide file tree
Showing 24 changed files with 111 additions and 182 deletions.
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<PackageVersion Include="FastEndpoints.Swagger" Version="5.19.0" />
<PackageVersion Include="FastEndpoints.Swagger.Swashbuckle" Version="2.2.0" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="MailKit" Version="4.2.0" />
<PackageVersion Include="MediatR" Version="12.1.1" />
<PackageVersion Include="MediatR.Extensions.Autofac.DependencyInjection" Version="11.2.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.0-rc.2.23480.2" />
Expand Down
11 changes: 4 additions & 7 deletions src/Clean.Architecture.Core/ContributorAggregate/Contributor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@

namespace Clean.Architecture.Core.ContributorAggregate;

public class Contributor : EntityBase, IAggregateRoot
public class Contributor(string name) : EntityBase, IAggregateRoot
{
public string Name { get; private set; }
// Example of validating primary constructor inputs
// See: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/primary-constructors#initialize-base-class
public string Name { get; private set; } = Guard.Against.NullOrEmpty(name, nameof(name));
public ContributorStatus Status { get; private set; } = ContributorStatus.NotSet;

public Contributor(string name)
{
Name = Guard.Against.NullOrEmpty(name, nameof(name));
}

public void UpdateName(string newName)
{
Name = Guard.Against.NullOrEmpty(newName, nameof(newName));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ namespace Clean.Architecture.Core.ContributorAggregate.Events;
/// A domain event that is dispatched whenever a contributor is deleted.
/// The DeleteContributorService is used to dispatch this event.
/// </summary>
internal class ContributorDeletedEvent : DomainEventBase
internal sealed class ContributorDeletedEvent(int contributorId) : DomainEventBase
{
public int ContributorId { get; set; }

public ContributorDeletedEvent(int contributorId)
{
ContributorId = contributorId;
}
public int ContributorId { get; init; } = contributorId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,8 @@ namespace Clean.Architecture.Core.ContributorAggregate.Handlers;
/// <summary>
/// NOTE: Internal because ContributorDeleted is also marked as internal.
/// </summary>
internal class ContributorDeletedHandler : INotificationHandler<ContributorDeletedEvent>
internal class ContributorDeletedHandler(ILogger<ContributorDeletedHandler> _logger) : INotificationHandler<ContributorDeletedEvent>
{
private readonly ILogger<ContributorDeletedHandler> _logger;

public ContributorDeletedHandler(ILogger<ContributorDeletedHandler> logger)
{
_logger = logger;
}

public async Task Handle(ContributorDeletedEvent domainEvent, CancellationToken cancellationToken)
{
_logger.LogInformation("Handling Contributed Deleted event for {contributorId}", domainEvent.ContributorId);
Expand Down
17 changes: 3 additions & 14 deletions src/Clean.Architecture.Core/Services/DeleteContributorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,10 @@

namespace Clean.Architecture.Core.Services;

public class DeleteContributorService : IDeleteContributorService
public class DeleteContributorService(IRepository<Contributor> _repository,
IMediator _mediator,
ILogger<DeleteContributorService> _logger) : IDeleteContributorService
{
private readonly IRepository<Contributor> _repository;
private readonly IMediator _mediator;
private readonly ILogger<DeleteContributorService> _logger;

public DeleteContributorService(IRepository<Contributor> repository,
IMediator mediator,
ILogger<DeleteContributorService> logger)
{
_repository = repository;
_mediator = mediator;
_logger = logger;
}

public async Task<Result> DeleteContributor(int contributorId)
{
_logger.LogInformation("Deleting Contributor {contributorId}", contributorId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ private void RegisterDevelopmentOnlyDependencies(ContainerBuilder builder)
builder.RegisterType<FakeListContributorsQueryService>()
.As<IListContributorsQueryService>()
.InstancePerLifetimeScope();

}

private void RegisterProductionOnlyDependencies(ContainerBuilder builder)
Expand All @@ -132,6 +131,5 @@ private void RegisterProductionOnlyDependencies(ContainerBuilder builder)
builder.RegisterType<ListContributorsQueryService>()
.As<IListContributorsQueryService>()
.InstancePerLifetimeScope();

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" />
<PackageReference Include="Autofac" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" />
<PackageReference Include="MailKit" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" PrivateAssets="all" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
Expand Down
10 changes: 0 additions & 10 deletions src/Clean.Architecture.Infrastructure/Data/AppDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,8 @@
using Ardalis.SharedKernel;
using Clean.Architecture.Core.ContributorAggregate;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace Clean.Architecture.Infrastructure.Data;

public static class AppDbContextExtensions
{
public static void AddApplicationDbContext(this IServiceCollection services, string connectionString)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlite(connectionString));
}
}
public class AppDbContext : DbContext
{
private readonly IDomainEventDispatcher? _dispatcher;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace Clean.Architecture.Infrastructure.Data;

public static class AppDbContextExtensions
{
public static void AddApplicationDbContext(this IServiceCollection services, string connectionString)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlite(connectionString));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,9 @@

namespace Clean.Architecture.Infrastructure.Data.Queries;

public class ListContributorsQueryService : IListContributorsQueryService
public class ListContributorsQueryService(AppDbContext _db) : IListContributorsQueryService
{
// You can use EF, Dapper, SqlClient, etc. for queries
private readonly AppDbContext _db;

public ListContributorsQueryService(AppDbContext db)
{
_db = db;
}
// You can use EF, Dapper, SqlClient, etc. for queries - this is just an example

public async Task<IEnumerable<ContributorDTO>> ListAsync()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,8 @@

namespace Clean.Architecture.Infrastructure.Email;

public class FakeEmailSender : IEmailSender
public class FakeEmailSender(ILogger<FakeEmailSender> _logger) : IEmailSender
{
private readonly ILogger<FakeEmailSender> _logger;

public FakeEmailSender(ILogger<FakeEmailSender> logger)
{
_logger = logger;
}
public Task SendEmailAsync(string to, string from, string subject, string body)
{
_logger.LogInformation("Not actually sending an email to {to} from {from} with subject {subject}", to, from, subject);
Expand Down
29 changes: 29 additions & 0 deletions src/Clean.Architecture.Infrastructure/Email/MimeKitEmailSender.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Clean.Architecture.Core.Interfaces;
using MailKit.Net.Smtp;
using Microsoft.Extensions.Logging;
using MimeKit;

namespace Clean.Architecture.Infrastructure.Email;

public class MimeKitEmailSender(ILogger<MimeKitEmailSender> _logger) : IEmailSender
{
public async Task SendEmailAsync(string to, string from, string subject, string body)
{
_logger.LogInformation("Attempting to send email to {to} from {from} with subject {subject}...", to, from, subject);

using (SmtpClient client = new SmtpClient()) // use localhost and a test server
{
client.Connect("localhost", 25, false); // TODO: pull settings from config
var message = new MimeMessage();
message.From.Add(new MailboxAddress(from, from));
message.To.Add(new MailboxAddress(to, to));
message.Subject = subject;
message.Body = new TextPart("plain") { Text = body };

await client.SendAsync(message);
_logger.LogInformation("Email sent!");

client.Disconnect(true);
}
}
}
11 changes: 2 additions & 9 deletions src/Clean.Architecture.Infrastructure/Email/SmtpEmailSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,11 @@

namespace Clean.Architecture.Infrastructure.Email;

public class SmtpEmailSender : IEmailSender
public class SmtpEmailSender(ILogger<SmtpEmailSender> _logger) : IEmailSender
{
private readonly ILogger<SmtpEmailSender> _logger;

public SmtpEmailSender(ILogger<SmtpEmailSender> logger)
{
_logger = logger;
}

public async Task SendEmailAsync(string to, string from, string subject, string body)
{
var emailClient = new SmtpClient("localhost");
var emailClient = new SmtpClient("localhost"); // TODO: pull settings from config
var message = new MailMessage
{
From = new MailAddress(from),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,9 @@

namespace Clean.Architecture.UseCases.Contributors.Create;

public class CreateContributorHandler : ICommandHandler<CreateContributorCommand, Result<int>>
public class CreateContributorHandler(IRepository<Contributor> _repository)
: ICommandHandler<CreateContributorCommand, Result<int>>
{
private readonly IRepository<Contributor> _repository;

public CreateContributorHandler(IRepository<Contributor> repository)
{
_repository = repository;
}

public async Task<Result<int>> Handle(CreateContributorCommand request,
CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,17 @@

namespace Clean.Architecture.UseCases.Contributors.Delete;

public class DeleteContributorHandler : ICommandHandler<DeleteContributorCommand, Result>
public class DeleteContributorHandler(IDeleteContributorService _deleteContributorService)
: ICommandHandler<DeleteContributorCommand, Result>
{
private readonly IDeleteContributorService _deleteContributorService;

public DeleteContributorHandler(IDeleteContributorService deleteContributorService)
{
_deleteContributorService = deleteContributorService;
}

public async Task<Result> Handle(DeleteContributorCommand request, CancellationToken cancellationToken)
{
// This Approach: Keep Domain Events in the Domain Model / Core project; this becomes a pass-through
// This is @ardalis's preferred approach
return await _deleteContributorService.DeleteContributor(request.ContributorId);

// Another Approach: Do the real work here including dispatching domain events - change the event from internal to public
// Ardalis prefers using the service so that "domain event" behavior remains in the domain model / core project
// @ardalis prefers using the service above so that **domain** event behavior remains in the **domain model** (core project)
// var aggregateToDelete = await _repository.GetByIdAsync(request.ContributorId);
// if (aggregateToDelete == null) return Result.NotFound();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,9 @@ namespace Clean.Architecture.UseCases.Contributors.Get;
/// <summary>
/// Queries don't necessarily need to use repository methods, but they can if it's convenient
/// </summary>
public class GetContributorHandler : IQueryHandler<GetContributorQuery, Result<ContributorDTO>>
public class GetContributorHandler(IReadRepository<Contributor> _repository)
: IQueryHandler<GetContributorQuery, Result<ContributorDTO>>
{
private readonly IReadRepository<Contributor> _repository;

public GetContributorHandler(IReadRepository<Contributor> repository)
{
_repository = repository;
}

public async Task<Result<ContributorDTO>> Handle(GetContributorQuery request, CancellationToken cancellationToken)
{
var spec = new ContributorByIdSpec(request.ContributorId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,9 @@

namespace Clean.Architecture.UseCases.Contributors.List;

public class ListContributorsHandler : IQueryHandler<ListContributorsQuery, Result<IEnumerable<ContributorDTO>>>
public class ListContributorsHandler(IListContributorsQueryService _query)
: IQueryHandler<ListContributorsQuery, Result<IEnumerable<ContributorDTO>>>
{
private readonly IListContributorsQueryService _query;

public ListContributorsHandler(IListContributorsQueryService query)
{
_query = query;
}

public async Task<Result<IEnumerable<ContributorDTO>>> Handle(ListContributorsQuery request, CancellationToken cancellationToken)
{
var result = await _query.ListAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,9 @@

namespace Clean.Architecture.UseCases.Contributors.Update;

public class UpdateContributorHandler : ICommandHandler<UpdateContributorCommand, Result<ContributorDTO>>
public class UpdateContributorHandler(IRepository<Contributor> _repository)
: ICommandHandler<UpdateContributorCommand, Result<ContributorDTO>>
{
private readonly IRepository<Contributor> _repository;

public UpdateContributorHandler(IRepository<Contributor> repository)
{
_repository = repository;
}

public async Task<Result<ContributorDTO>> Handle(UpdateContributorCommand request, CancellationToken cancellationToken)
{
var existingContributor = await _repository.GetByIdAsync(request.ContributorId, cancellationToken);
Expand Down
13 changes: 2 additions & 11 deletions src/Clean.Architecture.Web/Contributors/Create.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,9 @@ namespace Clean.Architecture.Web.ContributorEndpoints;
/// <remarks>
/// Creates a new Contributor given a name.
/// </remarks>
public class Create : Endpoint<CreateContributorRequest, CreateContributorResponse>
public class Create(IMediator _mediator)
: Endpoint<CreateContributorRequest, CreateContributorResponse>
{
private readonly IRepository<Contributor> _repository;
private readonly IMediator _mediator;

public Create(IRepository<Contributor> repository,
IMediator mediator)
{
_repository = repository;
_mediator = mediator;
}

public override void Configure()
{
Post(CreateContributorRequest.Route);
Expand Down
10 changes: 2 additions & 8 deletions src/Clean.Architecture.Web/Contributors/Delete.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,9 @@ namespace Clean.Architecture.Web.ContributorEndpoints;
/// <remarks>
/// Delete a Contributor by providing a valid integer id.
/// </remarks>
public class Delete : Endpoint<DeleteContributorRequest>
public class Delete(IMediator _mediator)
: Endpoint<DeleteContributorRequest>
{
private readonly IMediator _mediator;

public Delete(IMediator mediator)
{
_mediator = mediator;
}

public override void Configure()
{
Delete(DeleteContributorRequest.Route);
Expand Down
10 changes: 2 additions & 8 deletions src/Clean.Architecture.Web/Contributors/GetById.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,9 @@ namespace Clean.Architecture.Web.ContributorEndpoints;
/// <remarks>
/// Takes a positive integer ID and returns a matching Contributor record.
/// </remarks>
public class GetById : Endpoint<GetContributorByIdRequest, ContributorRecord>
public class GetById(IMediator _mediator)
: Endpoint<GetContributorByIdRequest, ContributorRecord>
{
private readonly IMediator _mediator;

public GetById(IMediator mediator)
{
_mediator = mediator;
}

public override void Configure()
{
Get(GetContributorByIdRequest.Route);
Expand Down
9 changes: 1 addition & 8 deletions src/Clean.Architecture.Web/Contributors/List.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,8 @@ namespace Clean.Architecture.Web.ContributorEndpoints;
/// <remarks>
/// List all contributors - returns a ContributorListResponse containing the Contributors.
/// </remarks>
public class List : EndpointWithoutRequest<ContributorListResponse>
public class List(IMediator _mediator) : EndpointWithoutRequest<ContributorListResponse>
{
private readonly IMediator _mediator;

public List(IMediator mediator)
{
_mediator = mediator;
}

public override void Configure()
{
Get("/Contributors");
Expand Down
Loading

0 comments on commit 631e5ac

Please sign in to comment.