Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 68 additions & 21 deletions Solution-Template.sln
Original file line number Diff line number Diff line change
@@ -1,21 +1,68 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microservice", "src\Api\Microservice.csproj", "{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microservice", "src\Api\Microservice.csproj", "{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infra", "src\Infra\Infra.csproj", "{73439EED-AE10-430D-99E5-2BD7E922BA8E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain", "src\Domain\Domain.csproj", "{A664D19C-0BDC-4B5F-AD31-C7BA0217DD22}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}.Debug|x64.ActiveCfg = Debug|Any CPU
{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}.Debug|x64.Build.0 = Debug|Any CPU
{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}.Debug|x86.ActiveCfg = Debug|Any CPU
{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}.Debug|x86.Build.0 = Debug|Any CPU
{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}.Release|Any CPU.Build.0 = Release|Any CPU
{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}.Release|x64.ActiveCfg = Release|Any CPU
{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}.Release|x64.Build.0 = Release|Any CPU
{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}.Release|x86.ActiveCfg = Release|Any CPU
{AC3F0F1B-31CD-47D4-953F-FC970261B9FB}.Release|x86.Build.0 = Release|Any CPU
{73439EED-AE10-430D-99E5-2BD7E922BA8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{73439EED-AE10-430D-99E5-2BD7E922BA8E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{73439EED-AE10-430D-99E5-2BD7E922BA8E}.Debug|x64.ActiveCfg = Debug|Any CPU
{73439EED-AE10-430D-99E5-2BD7E922BA8E}.Debug|x64.Build.0 = Debug|Any CPU
{73439EED-AE10-430D-99E5-2BD7E922BA8E}.Debug|x86.ActiveCfg = Debug|Any CPU
{73439EED-AE10-430D-99E5-2BD7E922BA8E}.Debug|x86.Build.0 = Debug|Any CPU
{73439EED-AE10-430D-99E5-2BD7E922BA8E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{73439EED-AE10-430D-99E5-2BD7E922BA8E}.Release|Any CPU.Build.0 = Release|Any CPU
{73439EED-AE10-430D-99E5-2BD7E922BA8E}.Release|x64.ActiveCfg = Release|Any CPU
{73439EED-AE10-430D-99E5-2BD7E922BA8E}.Release|x64.Build.0 = Release|Any CPU
{73439EED-AE10-430D-99E5-2BD7E922BA8E}.Release|x86.ActiveCfg = Release|Any CPU
{73439EED-AE10-430D-99E5-2BD7E922BA8E}.Release|x86.Build.0 = Release|Any CPU
{A664D19C-0BDC-4B5F-AD31-C7BA0217DD22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A664D19C-0BDC-4B5F-AD31-C7BA0217DD22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A664D19C-0BDC-4B5F-AD31-C7BA0217DD22}.Debug|x64.ActiveCfg = Debug|Any CPU
{A664D19C-0BDC-4B5F-AD31-C7BA0217DD22}.Debug|x64.Build.0 = Debug|Any CPU
{A664D19C-0BDC-4B5F-AD31-C7BA0217DD22}.Debug|x86.ActiveCfg = Debug|Any CPU
{A664D19C-0BDC-4B5F-AD31-C7BA0217DD22}.Debug|x86.Build.0 = Debug|Any CPU
{A664D19C-0BDC-4B5F-AD31-C7BA0217DD22}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A664D19C-0BDC-4B5F-AD31-C7BA0217DD22}.Release|Any CPU.Build.0 = Release|Any CPU
{A664D19C-0BDC-4B5F-AD31-C7BA0217DD22}.Release|x64.ActiveCfg = Release|Any CPU
{A664D19C-0BDC-4B5F-AD31-C7BA0217DD22}.Release|x64.Build.0 = Release|Any CPU
{A664D19C-0BDC-4B5F-AD31-C7BA0217DD22}.Release|x86.ActiveCfg = Release|Any CPU
{A664D19C-0BDC-4B5F-AD31-C7BA0217DD22}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{73439EED-AE10-430D-99E5-2BD7E922BA8E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{A664D19C-0BDC-4B5F-AD31-C7BA0217DD22} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
EndGlobalSection
EndGlobal
9 changes: 9 additions & 0 deletions src/Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
using Api.Middlewares;
using Asp.Versioning;
using Asp.Versioning.Builder;
using Domain.Interfaces;
using Infra.Data;
using Infra.Repositories;
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
using Prometheus;
using Scalar.AspNetCore;
Expand All @@ -13,6 +17,11 @@

builder.Services.AddControllers();

builder.Services
.AddDbContext<DataContext>(opts => opts.UseInMemoryDatabase("DemoDb"))
.AddScoped(typeof(IRepository<>), typeof(Repository<>))
.AddScoped<IUnitOfWork, UnitOfWork>();

builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
Expand Down
Empty file removed src/Domain/Contracts/.gitkeep
Empty file.
2 changes: 1 addition & 1 deletion src/Domain/Domain.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<RootNamespace>Domain</RootNamespace>
<Nullable>disable</Nullable>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
8 changes: 8 additions & 0 deletions src/Domain/Entities/BaseEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Domain.Entities;

public abstract class BaseEntity<TKey>
{
public TKey Id { get; set; } = default!;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}
6 changes: 6 additions & 0 deletions src/Domain/Entities/SampleEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Domain.Entities;

public class SampleEntity : BaseEntity<Guid>
{
public string Name { get; set; } = default!;
}
22 changes: 22 additions & 0 deletions src/Domain/Interfaces/IRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Linq.Expressions;

namespace Domain.Interfaces;

public interface IRepository<TEntity> : IAsyncDisposable where TEntity : class
{
IQueryable<TEntity> Query(bool asNoTracking = true);

Task<TEntity?> GetByIdAsync<TKey>(TKey id, CancellationToken cancellationToken = default);

Task<List<TEntity>> ListAsync(Expression<Func<TEntity, bool>>? predicate = null, CancellationToken cancellationToken = default);

Task<TEntity> AddAsync(TEntity entity, CancellationToken cancellationToken = default);

Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default);

Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default);

Task<int> DeleteAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);

Task<int> UpdateAsync(Expression<Func<TEntity, bool>> predicate, object updateDefinition, CancellationToken cancellationToken = default);
}
9 changes: 9 additions & 0 deletions src/Domain/Interfaces/IUnitOfWork.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Domain.Interfaces;

public interface IUnitOfWork : IAsyncDisposable
{
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);

// TODO: implement transaction management
// Task<object> BeginTransactionAsync(CancellationToken cancellationToken = default);
}
Empty file removed src/Infra/Data/.gitkeep
Empty file.
10 changes: 10 additions & 0 deletions src/Infra/Data/DataContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Domain.Entities;
using Microsoft.EntityFrameworkCore;

namespace Infra.Data;
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> opts) : base(opts) { }

public virtual DbSet<SampleEntity> Products => Set<SampleEntity>();
}
11 changes: 10 additions & 1 deletion src/Infra/Infra.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<RootNamespace>Infra</RootNamespace>
<Nullable>disable</Nullable>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../Domain/Domain.csproj" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.4" />
</ItemGroup>
</Project>
78 changes: 78 additions & 0 deletions src/Infra/Repositories/Repository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using Infra.Data;
using Domain.Interfaces;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query;

namespace Infra.Repositories;

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
private readonly DataContext _dataContext;
private readonly DbSet<TEntity> _dbSet;
private int _disposed;

public Repository(DataContext dataContext)
{
_dataContext = dataContext;
_dbSet = dataContext.Set<TEntity>();
}

public IQueryable<TEntity> Query(bool asNoTracking = true) => (asNoTracking ? _dbSet.AsNoTracking() : _dbSet).AsQueryable();

public Task<TEntity?> GetByIdAsync<TKey>(TKey id, CancellationToken cancellationToken = default)
{
return _dbSet.FindAsync(new object?[] { id }!, cancellationToken).AsTask();
}

public Task<List<TEntity>> ListAsync(Expression<Func<TEntity, bool>>? predicate = null, CancellationToken cancellationToken = default)
{
IQueryable<TEntity> query = _dbSet;
if (predicate is not null) query = query.Where(predicate);
return query.ToListAsync(cancellationToken);
}

public async Task<TEntity> AddAsync(TEntity entity, CancellationToken cancellationToken = default)
{
await _dbSet.AddAsync(entity, cancellationToken);

return entity;
}

public Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
{
_dbSet.Update(entity);

return Task.CompletedTask;
}

public Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default)
{
_dbSet.Remove(entity);

return Task.CompletedTask;
}

public Task<int> DeleteAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
{
return _dbSet.Where(predicate).ExecuteDeleteAsync(cancellationToken);
}

public Task<int> UpdateAsync(Expression<Func<TEntity, bool>> predicate, object updateDefinition, CancellationToken cancellationToken = default)
{
if (updateDefinition is not Expression<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>> expression)
{
throw new InvalidOperationException($"Invalid update definition for type '{typeof(TEntity).Name}'. Expected an expression of type Expression<Func<SetPropertyCalls<{typeof(TEntity).Name}>, SetPropertyCalls<{typeof(TEntity).Name}>>>.");
}

return _dbSet.Where(predicate).ExecuteUpdateAsync(expression, cancellationToken);
}

public async ValueTask DisposeAsync()
{
if (Interlocked.Exchange(ref _disposed, 1) == 0)
{
await _dataContext.DisposeAsync();
}
}
}
22 changes: 22 additions & 0 deletions src/Infra/Repositories/UnitOfWork.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Infra.Data;
using Domain.Interfaces;

namespace Infra.Repositories;

public class UnitOfWork : IUnitOfWork
{
private readonly DataContext _dataContext;
private int _disposed;

public UnitOfWork(DataContext dataContext) => _dataContext = dataContext;

public Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) => _dataContext.SaveChangesAsync(cancellationToken);

public async ValueTask DisposeAsync()
{
if (Interlocked.Exchange(ref _disposed, 1) == 0)
{
await _dataContext.DisposeAsync();
}
}
}