play with storing valueobjects

This commit is contained in:
Claudiu Farcas
2022-10-18 00:03:51 +03:00
parent 80ccd22d9c
commit b31cbcc15d
36 changed files with 250 additions and 81 deletions
@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Mvc;
using PracticeCalendar.API.Controllers;
using PracticeCalendar.Application.Products.Queries;
using PracticeCalendar.Application.Products.Queries.GetProducts;
namespace PracticeCalendar.Api.Controllers
{
public class ProductsController : ApiControllerBase
{
private readonly ILogger<ProductsController> logger;
public ProductsController(ILogger<ProductsController> logger)
{
this.logger = logger;
}
[HttpGet]
public async Task<ActionResult<List<ProductDto>>> Get()
{
return await Mediator.Send(new GetProductsQuery());
}
}
}
+4 -6
View File
@@ -9,7 +9,7 @@ namespace PracticeCalendar
{
public class Program
{
public static void Main(string[] args)
public static async Task Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
@@ -51,13 +51,11 @@ namespace PracticeCalendar
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<ApplicationDbContext>();
//context.Database.Migrate();
context.Database.EnsureCreated();
//SeedData.Initialize(services);
var initialiser = services.GetRequiredService<ApplicationDbContextInitialiser>();
await initialiser.InitialiseAsync();
await initialiser.SeedAsync();
}
catch (Exception ex)
{
Binary file not shown.
@@ -3,7 +3,10 @@ using MapsterMapper;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using PracticeCalendar.Application.PracticeEvents.Queries;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Application.Products.Queries;
using PracticeCalendar.Domain.Entities.PracticeEvent;
using PracticeCalendar.Domain.Entities.Product;
using PracticeCalendar.Domain.ValueObjects;
using System.Reflection;
namespace PracticeCalendar.Application
@@ -23,11 +26,35 @@ namespace PracticeCalendar.Application
TypeAdapterConfig.GlobalSettings.Default.MapToConstructor(true);
TypeAdapterConfig.GlobalSettings.NewConfig<PracticeEventDto, PracticeEvent>()
.ConstructUsing(src => new PracticeEvent(src.Title, src.Description, src.StartTime, src.EndTime));
var mapsterConfig = new TypeAdapterConfig();
mapsterConfig.NewConfig<PracticeEventDto, PracticeEvent>()
.MapToConstructor(true)
.ConstructUsing(src => new PracticeEvent(src.Title, src.Description, src.StartTime, src.EndTime));
mapsterConfig.NewConfig<ProductDto, Product>()
.MapToConstructor(true)
.ConstructUsing(src => new Product()
{
Id = src.Id,
Category = src.Category,
Name = src.Name,
UnitPrice = new Price
{
Value = src.UnitPrice,
Currency = src.UnitPriceCurrency
}
});
mapsterConfig
.ForType<Product, ProductDto>()
.MapWith(src => new ProductDto
{
Id = src.Id,
Name = src.Name,
Category = src.Category,
UnitPrice = src.UnitPrice.Value,
UnitPriceCurrency = src.UnitPrice.Currency
})
;
services.AddSingleton<IMapper>(new Mapper(mapsterConfig));
return services;
@@ -1,7 +1,7 @@
using MediatR;
using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.Specifications;
using PracticeCalendar.Domain.Entities.PracticeEvent;
using PracticeCalendar.Domain.Entities.PracticeEvent.Specifications;
using PracticeCalendar.Domain.Exceptions;
namespace PracticeCalendar.Application.PracticeEvents.Commands
@@ -29,7 +29,7 @@ namespace PracticeCalendar.Application.PracticeEvents.Commands
public async Task<Unit> Handle(AttendeeAcceptEventCommand request, CancellationToken cancellationToken)
{
var spec = new PracticeEventByIdWithAttendees(request.EventId);
var spec = new PracticeEventByIdWithAttendeesSpecification(request.EventId);
var practiceEvent = await eventsRepo.FirstOrDefaultAsync(spec, cancellationToken);
if (practiceEvent == null)
{
@@ -37,7 +37,7 @@ namespace PracticeCalendar.Application.PracticeEvents.Commands
}
practiceEvent.AttendeeAcceptEvent(request.AttendeeId);
await eventsRepo.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
}
@@ -1,7 +1,7 @@
using MediatR;
using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.Specifications;
using PracticeCalendar.Domain.Entities.PracticeEvent;
using PracticeCalendar.Domain.Entities.PracticeEvent.Specifications;
using PracticeCalendar.Domain.Exceptions;
namespace PracticeCalendar.Application.PracticeEvents.Commands
@@ -29,14 +29,14 @@ namespace PracticeCalendar.Application.PracticeEvents.Commands
public async Task<Unit> Handle(AttendeeDeclineEventCommand request, CancellationToken cancellationToken)
{
var spec = new PracticeEventByIdWithAttendees(request.EventId);
var spec = new PracticeEventByIdWithAttendeesSpecification(request.EventId);
var practiceEvent = await eventsRepo.FirstOrDefaultAsync(spec, cancellationToken);
if (practiceEvent == null)
{
throw new PracticeEventNotFoundException();
}
practiceEvent.AttendeeDeclineEvent(request.AttendeeId);
return Unit.Value;
}
}
@@ -2,7 +2,7 @@
using MediatR;
using PracticeCalendar.Application.PracticeEvents.Queries;
using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.PracticeEvent;
namespace PracticeCalendar.Application.PracticeEvents.Commands
{
@@ -1,6 +1,6 @@
using MediatR;
using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.PracticeEvent;
using PracticeCalendar.Domain.Exceptions;
namespace PracticeCalendar.Application.PracticeEvents.Commands
@@ -32,7 +32,7 @@ namespace PracticeCalendar.Application.PracticeEvents.Commands
}
await eventsRepo.DeleteAsync(org, cancellationToken);
await eventsRepo.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
}
@@ -2,7 +2,7 @@
using MediatR;
using PracticeCalendar.Application.PracticeEvents.Queries;
using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.PracticeEvent;
using PracticeCalendar.Domain.Exceptions;
namespace PracticeCalendar.Application.PracticeEvents.Commands
@@ -3,8 +3,8 @@ using MapsterMapper;
using MediatR;
using Microsoft.Extensions.Logging;
using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.Specifications;
using PracticeCalendar.Domain.Entities.PracticeEvent;
using PracticeCalendar.Domain.Entities.PracticeEvent.Specifications;
namespace PracticeCalendar.Application.PracticeEvents.Queries.GetPracticeEvents
{
@@ -29,7 +29,7 @@ namespace PracticeCalendar.Application.PracticeEvents.Queries.GetPracticeEvents
public async Task<List<PracticeEventDto>> Handle(GetPracticeEventsQuery request, CancellationToken cancellationToken)
{
var spec = new PracticeEventsWithAttendees();
var spec = new PracticeEventsWithAttendeesSpecification();
var evList = await eventsRepo.ListAsync(spec, cancellationToken);
var lst = evList.Adapt<List<PracticeEventDto>>(mapper.Config);
return lst;
@@ -0,0 +1,39 @@
using Mapster;
using MapsterMapper;
using MediatR;
using Microsoft.Extensions.Logging;
using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.Entities.Product;
using PracticeCalendar.Domain.Entities.Product.Specifications;
namespace PracticeCalendar.Application.Products.Queries.GetProducts
{
public class GetProductsQuery : IRequest<List<ProductDto>>
{
}
public class GetProductsQueryHandler : IRequestHandler<GetProductsQuery, List<ProductDto>>
{
private readonly ILogger<GetProductsQueryHandler> logger;
private readonly IRepository<Product> eventsRepo;
private readonly IMapper mapper;
public GetProductsQueryHandler(IRepository<Product> eventsRepo,
ILogger<GetProductsQueryHandler> logger,
IMapper mapper)
{
this.eventsRepo = eventsRepo;
this.logger = logger;
this.mapper = mapper;
}
public async Task<List<ProductDto>> Handle(GetProductsQuery request, CancellationToken cancellationToken)
{
var spec = new AllProductsSpecification();
var evList = await eventsRepo.ListAsync(spec, cancellationToken);
var lst = evList.Adapt<List<ProductDto>>(mapper.Config);
return lst;
}
}
}
@@ -0,0 +1,13 @@
using PracticeCalendar.Domain.ValueObjects;
namespace PracticeCalendar.Application.Products.Queries
{
public class ProductDto
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Category { get; set; } = string.Empty;
public decimal UnitPrice { get; set; }
public string UnitPriceCurrency { get; set; } = Price.DEFAULT_CURRENCY;
}
}
@@ -1,12 +1,12 @@
using Ardalis.GuardClauses;
using PracticeCalendar.Domain.Common;
namespace PracticeCalendar.Domain.Entities
namespace PracticeCalendar.Domain.Entities.PracticeEvent
{
/// <summary>
/// Attendee to an event
/// </summary>
public class Attendee: EntityBase
public class Attendee : EntityBase
{
public Attendee(string name, string emailAddress)
{
@@ -20,12 +20,12 @@ namespace PracticeCalendar.Domain.Entities
public string Name { get; set; } = string.Empty;
public string EmailAddress { get; set; } = string.Empty;
public bool IsAttending { get; private set; }
/// <summary>
/// Set if the Attendee is attending
/// </summary>
/// <param name="isAttending"></param>
public void SetIsAttending (bool isAttending)
public void SetIsAttending(bool isAttending)
{
this.IsAttending = isAttending;
//TODO - raise event
@@ -4,7 +4,7 @@ using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.Events;
using PracticeCalendar.Domain.Exceptions;
namespace PracticeCalendar.Domain.Entities
namespace PracticeCalendar.Domain.Entities.PracticeEvent
{
/// <summary>
/// Practice event aggregate
@@ -22,7 +22,7 @@ namespace PracticeCalendar.Domain.Entities
}
public string Title { get; private set; } = string.Empty;
public string Description{ get; private set; } = string.Empty;
public string Description { get; private set; } = string.Empty;
public IList<Attendee> Attendees { get; private set; } = new List<Attendee>();
@@ -0,0 +1,13 @@
using Ardalis.Specification;
namespace PracticeCalendar.Domain.Entities.PracticeEvent.Specifications
{
public class PracticeEventByIdWithAttendeesSpecification : Specification<PracticeEvent>
{
public PracticeEventByIdWithAttendeesSpecification(int eventId)
{
Query.Where(x => x.Id == eventId)
.Include(x => x.Attendees);
}
}
}
@@ -0,0 +1,13 @@
using Ardalis.Specification;
namespace PracticeCalendar.Domain.Entities.PracticeEvent.Specifications
{
public class PracticeEventsWithAttendeesSpecification : Specification<PracticeEvent>
{
public PracticeEventsWithAttendeesSpecification()
{
Query.AsNoTracking()
.Include(x => x.Attendees);
}
}
}
@@ -0,0 +1,13 @@
using PracticeCalendar.Domain.Common;
using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.ValueObjects;
namespace PracticeCalendar.Domain.Entities.Product
{
public class Product : EntityBase, IAggregateRoot
{
public string Name { get; set; } = string.Empty;
public string Category { get; set; } = string.Empty;
public Price UnitPrice { get; set; } = Price.Empty;
}
}
@@ -0,0 +1,12 @@
using Ardalis.Specification;
namespace PracticeCalendar.Domain.Entities.Product.Specifications
{
public class AllProductsSpecification : Specification<Product>
{
public AllProductsSpecification()
{
Query.AsNoTracking();
}
}
}
@@ -1,13 +0,0 @@
using Ardalis.Specification;
namespace PracticeCalendar.Domain.Entities.Specifications
{
public class PracticeEventByIdWithAttendees : Specification<PracticeEvent>
{
public PracticeEventByIdWithAttendees(int eventId)
{
Query.Where(x=>x.Id == eventId)
.Include(x => x.Attendees);
}
}
}
@@ -1,13 +0,0 @@
using Ardalis.Specification;
namespace PracticeCalendar.Domain.Entities.Specifications
{
public class PracticeEventsWithAttendees : Specification<PracticeEvent>
{
public PracticeEventsWithAttendees()
{
Query.AsNoTracking()
.Include(x => x.Attendees);
}
}
}
@@ -1,5 +1,5 @@
using PracticeCalendar.Domain.Common;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.PracticeEvent;
namespace PracticeCalendar.Domain.Events
{
@@ -1,5 +1,5 @@
using PracticeCalendar.Domain.Common;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.PracticeEvent;
namespace PracticeCalendar.Domain.Events
{
@@ -1,5 +1,5 @@
using PracticeCalendar.Domain.Common;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.PracticeEvent;
namespace PracticeCalendar.Domain.Events
{
@@ -1,5 +1,5 @@
using PracticeCalendar.Domain.Common;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.PracticeEvent;
namespace PracticeCalendar.Domain.Events
{
@@ -0,0 +1,13 @@
using PracticeCalendar.Domain.Common;
namespace PracticeCalendar.Domain.ValueObjects
{
public class Price : ValueObject
{
public static Price Empty = new Price();
public static string DEFAULT_CURRENCY = "EUR";
public decimal Value { get; set; }
public string Currency { get; set; } = Price.DEFAULT_CURRENCY;
}
}
@@ -11,12 +11,13 @@ namespace PracticeCalendar.Infrastructure
{
public static class ConfigureServices
{
public static IServiceCollection AddInfrastructureServices(this IServiceCollection services,
public static IServiceCollection AddInfrastructureServices(this IServiceCollection services,
IConfigurationRoot configuration)
{
string connectionString = configuration.GetConnectionString("SqliteConnection");
services.AddDbContext(connectionString);
services.AddTransient<IEmailSender, FileEmailSender>();
services.AddSingleton<IDomainEventService, DomainEventService>();
@@ -25,8 +26,11 @@ namespace PracticeCalendar.Infrastructure
return services;
}
public static void AddDbContext(this IServiceCollection services, string connectionString) =>
public static void AddDbContext(this IServiceCollection services, string connectionString)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(connectionString));
services.AddScoped<ApplicationDbContextInitialiser>();
}
}
}
@@ -1,7 +1,8 @@
using Microsoft.EntityFrameworkCore;
using PracticeCalendar.Domain.Common;
using PracticeCalendar.Domain.Common.Interfaces;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.PracticeEvent;
using PracticeCalendar.Domain.Entities.Product;
using System.Reflection;
namespace PracticeCalendar.Infrastructure.Persistence
@@ -13,6 +14,8 @@ namespace PracticeCalendar.Infrastructure.Persistence
public DbSet<Attendee> Atendees => Set<Attendee>();
public DbSet<PracticeEvent> PracticeEvents => Set<PracticeEvent>();
public DbSet<Product> Products => Set<Product>();
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IDomainEventService domainEventService)
: base(options)
{
@@ -1,11 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PracticeCalendar.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using PracticeCalendar.Domain.Entities.PracticeEvent;
namespace PracticeCalendar.Infrastructure.Persistence
{
@@ -14,7 +9,7 @@ namespace PracticeCalendar.Infrastructure.Persistence
private readonly ILogger<ApplicationDbContextInitialiser> logger;
private readonly ApplicationDbContext context;
public ApplicationDbContextInitialiser(ILogger<ApplicationDbContextInitialiser> logger,
public ApplicationDbContextInitialiser(ILogger<ApplicationDbContextInitialiser> logger,
ApplicationDbContext context)
{
this.logger = logger;
@@ -27,6 +22,7 @@ namespace PracticeCalendar.Infrastructure.Persistence
{
if (context.Database.IsSqlite())
{
await context.Database.EnsureCreatedAsync();
await context.Database.MigrateAsync();
}
}
@@ -56,7 +52,7 @@ namespace PracticeCalendar.Infrastructure.Persistence
// Seed, if necessary
if (!context.PracticeEvents.Any())
{
context.PracticeEvents.Add(new PracticeEvent("Event 1", "Event 1 desc",
context.PracticeEvents.Add(new PracticeEvent("Event 1", "Event 1 desc",
DateTime.Now.AddHours(-1),
DateTime.Now.AddHours(1)));
@@ -1,6 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.PracticeEvent;
namespace PracticeCalendar.Infrastructure.Persistence.Configuration
{
@@ -1,6 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.PracticeEvent;
namespace PracticeCalendar.Infrastructure.Persistence.Configuration
{
@@ -0,0 +1,30 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using PracticeCalendar.Domain.Entities.Product;
using PracticeCalendar.Domain.ValueObjects;
namespace PracticeCalendar.Infrastructure.Persistence.Configuration
{
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.Property(x => x.Name)
.HasMaxLength(100)
.IsRequired();
builder.Property(x => x.Category)
.HasMaxLength(100)
.IsRequired();
builder.OwnsOne(product => product.UnitPrice, price =>
{
price.Property(p => p.Currency)
.HasColumnName(nameof(Price.Currency))
.HasMaxLength(5)
.IsRequired();
price.Property(p => p.Value)
.HasColumnName(nameof(Price.Value))
.IsRequired();
});
}
}
}
@@ -1,7 +1,7 @@
using FluentAssertions;
using Mapster;
using PracticeCalendar.Application.PracticeEvents.Queries;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.PracticeEvent;
namespace PracticeCalendar.UnitTests.Application.Mapping
{
@@ -1,6 +1,5 @@
using FluentAssertions;
using FluentAssertions.Common;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.PracticeEvent;
namespace PracticeCalendar.UnitTests.Domain
{
@@ -1,7 +1,6 @@
using FluentAssertions;
using PracticeCalendar.Application.PracticeEvents.Queries.GetPracticeEvents;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.PracticeEvent;
using static PracticeCalendar.UnitTests.Integration.Testing;
namespace PracticeCalendar.UnitTests.Integration.PracticeEvents
@@ -26,11 +25,11 @@ namespace PracticeCalendar.UnitTests.Integration.PracticeEvents
{
await RunBeforeAnyTests();
await AddAsync(new PracticeEvent("Test Event", "Event description",
await AddAsync(new PracticeEvent("Test Event", "Event description",
DateTime.Now.AddHours(-1), DateTime.Now.AddHours(1))
{
Id = 1,
Attendees = {
Attendees = {
new Attendee("Claudiu F", "claudiuf@busybee.com")
{
Id = 1
@@ -4,9 +4,9 @@ using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using PracticeCalendar.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Common.Interfaces;
using Moq;
using PracticeCalendar.Domain.Entities.PracticeEvent;
namespace PracticeCalendar.UnitTests.Integration
{
@@ -1,6 +1,6 @@
using System.Net;
using FluentAssertions;
using PracticeCalendar.Domain.Entities;
using PracticeCalendar.Domain.Entities.PracticeEvent;
using PracticeCalendar.UnitTests.Integration;
using static PracticeCalendar.UnitTests.Integration.Testing;