Skip to content

Commit

Permalink
feat: Implement CQRS architecture
Browse files Browse the repository at this point in the history
* This commit introduces the implementation of the Command Query Responsibility Segregation (CQRS) pattern.
* Entities representing commands and queries are created to encapsulate actions and data retrieval, respectively.
* Additionally, corresponding command and query handlers are implemented to execute these actions.
* The service responsible for PDF generation is refactored to utilize CQRS principles, improving separation of concerns and modularity.
  • Loading branch information
Pedrolustosa committed Jun 4, 2024
1 parent a67dda9 commit 4979921
Show file tree
Hide file tree
Showing 14 changed files with 208 additions and 147 deletions.
21 changes: 15 additions & 6 deletions CV.API/Controllers/CurriculumController.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
using MediatR;
using CV.Domain.Entity;
using CV.Application.Interface;
using Microsoft.AspNetCore.Mvc;
using CV.Application.CQRS.Commands;

namespace CV.API.Controllers;

[Route("api/[controller]")]
[ApiController]
public class CurriculumController(ICurriculumService curriculumService) : ControllerBase
public class CurriculumController(IMediator mediator) : ControllerBase
{
private readonly ICurriculumService _curriculumService = curriculumService;
private readonly IMediator _mediator = mediator;

[HttpPost]
[Route("GenerateCurriculum")]
public IActionResult GerarCurriculum([FromBody] Curriculum curriculum)
public async Task<IActionResult> GerarCurriculum([FromBody] Curriculum curriculum)
{
var pdfBytes = _curriculumService.GerarCurriculoPdf(curriculum);
return File(pdfBytes, "application/pdf", $"{curriculum.Name}.pdf");
try
{
var command = new CreateCurriculumCommand { Curriculum = curriculum };
var pdfBytes = await _mediator.Send(command);
return File(pdfBytes, "application/pdf", $"{curriculum.Name}.pdf");
}
catch (Exception)
{
throw;
}
}
}
9 changes: 9 additions & 0 deletions CV.Application/CQRS/Commands/CreateCurriculumCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using MediatR;
using CV.Domain.Entity;

namespace CV.Application.CQRS.Commands;

public class CreateCurriculumCommand : IRequest<byte[]>
{
public Curriculum Curriculum { get; set; }
}
24 changes: 24 additions & 0 deletions CV.Application/CQRS/Handlers/CreateCurriculumCommandHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using MediatR;
using CV.Application.Interface;
using CV.Application.CQRS.Commands;

namespace CV.Application.CQRS.Handlers;

public class CreateCurriculumCommandHandler(ICurriculumService curriculumService) : IRequestHandler<CreateCurriculumCommand, byte[]>
{
private readonly ICurriculumService _curriculumService = curriculumService;

public async Task<byte[]> Handle(CreateCurriculumCommand request, CancellationToken cancellationToken)
{
try
{
var pdf = await _curriculumService.GeneratePdf(request.Curriculum);
return pdf;
}
catch (Exception)
{
throw;
}

}
}
4 changes: 2 additions & 2 deletions CV.Application/CV.Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
<ItemGroup>
<PackageReference Include="itext7" Version="8.0.4" />
<PackageReference Include="itext7.bouncy-castle-adapter" Version="8.0.4" />
<PackageReference Include="iTextSharp" Version="5.5.13.3" />
<PackageReference Include="MediatR" Version="11.1.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CV.Domain\CV.Domain.csproj" />
<ProjectReference Include="..\CV.Infra\CV.Infra.csproj" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion CV.Application/Interface/ICurriculumService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ namespace CV.Application.Interface;

public interface ICurriculumService
{
byte[] GerarCurriculoPdf(Curriculum curriculo);
Task<byte[]> GeneratePdf(Curriculum curriculo);
}
190 changes: 103 additions & 87 deletions CV.Application/Service/CurriculumService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using CV.Domain.Entity;
using iText.Kernel.Pdf;
using iText.Kernel.Font;
using iText.Kernel.Geom;
using iText.Kernel.Colors;
using iText.Layout.Element;
using iText.Layout.Properties;
Expand All @@ -14,98 +13,115 @@ namespace CV.Application.Service;

public class CurriculumService : ICurriculumService
{
public byte[] GerarCurriculoPdf(Curriculum curriculum)
public async Task<byte[]> GeneratePdf(Curriculum curriculum)
{
using var memoryStream = new MemoryStream();
PdfWriter writer = new(memoryStream);
PdfDocument pdf = new(writer);
PageSize pageSize = PageSize.A4;
Document document = new(pdf, pageSize);

document.SetMargins(36, 36, 36, 36);
PdfFont bold = PdfFontFactory.CreateFont(StandardFonts.HELVETICA_BOLD);
PdfFont normal = PdfFontFactory.CreateFont(StandardFonts.HELVETICA);

var titleStyle = new Style()
.SetFontSize(24)
.SetBold()
.SetFont(bold)
.SetTextAlignment(TextAlignment.CENTER);

var sectionTitleStyle = new Style()
.SetFontSize(14)
.SetBold()
.SetFont(bold);

var textStyle = new Style()
.SetFontSize(12)
.SetFont(normal);

var bulletStyle = new Style()
.SetFontSize(10)
.SetFont(normal);

var subBulletStyle = new Style()
.SetFontSize(10)
.SetFont(normal)
.SetMarginLeft(15);

document.Add(new Paragraph(curriculum.Name).AddStyle(titleStyle));
document.Add(new Paragraph($" Contato: {curriculum.Contact}").AddStyle(textStyle));
document.Add(new Paragraph($" Endereço: {curriculum.Address}").AddStyle(textStyle));
document.Add(new Paragraph($" Telefone: {curriculum.Telephone}").AddStyle(textStyle));
document.Add(new Paragraph($" Email: {curriculum.Email}").AddStyle(textStyle));

Paragraph links = new Paragraph()
.Add(new Link("GitHub", PdfAction.CreateURI(curriculum.GitHub)).SetUnderline().SetFontColor(ColorConstants.BLUE).AddStyle(textStyle))
try
{
using var memoryStream = new MemoryStream();
PdfWriter writer = new(memoryStream);
PdfDocument pdf = new(writer);
Document document = new(pdf);
float marginLeft = 3 * 72 / 2.54f;
float marginRight = 2 * 72 / 2.54f;
float marginTop = 3 * 72 / 2.54f;
float marginBottom = 2 * 72 / 2.54f;

document.SetMargins(marginLeft, marginRight, marginTop, marginBottom);
PdfFont bold = PdfFontFactory.CreateFont(StandardFonts.HELVETICA_BOLD);
PdfFont normal = PdfFontFactory.CreateFont(StandardFonts.HELVETICA);

var contactTitleStyle = new Style()
.SetFontSize(28)
.SetBold()
.SetFont(bold)
.SetMarginBottom(5)
.SetTextAlignment(TextAlignment.CENTER);

var contactDetailsStyle = new Style()
.SetFontSize(10)
.SetFont(normal);

var sectionTitleStyle = new Style()
.SetFontSize(14)
.SetBold()
.SetFont(bold);

var textStyle = new Style()
.SetFontSize(12)
.SetFont(normal);

var bulletStyle = new Style()
.SetFontSize(12)
.SetFont(normal);

var subBulletStyle = new Style()
.SetFontSize(12)
.SetFont(normal)
.SetMarginLeft(15);

#region Contato
document.Add(new Paragraph($"{curriculum.Name}").AddStyle(contactTitleStyle));
document.Add(new Paragraph($"Endereço: {curriculum.Contact.Address}").AddStyle(contactDetailsStyle));
document.Add(new Paragraph($"Telefone: {curriculum.Contact.Telephone}").AddStyle(contactDetailsStyle));
document.Add(new Paragraph($"Email: {curriculum.Contact.Email}").AddStyle(contactDetailsStyle));

Paragraph links = new Paragraph()
.Add(new Link("GitHub", PdfAction.CreateURI(curriculum.Contact.GitHub)).SetUnderline().SetFontColor(ColorConstants.BLUE).AddStyle(textStyle))
.Add(new Text(" | ").AddStyle(textStyle))
.Add(new Link("LinkedIn", PdfAction.CreateURI(curriculum.LinkedIn)).SetUnderline().SetFontColor(ColorConstants.BLUE).AddStyle(textStyle));
document.Add(links);

Div educationSection = new Div().SetKeepTogether(true);
educationSection.Add(new Paragraph("Educação:").AddStyle(sectionTitleStyle));

foreach (var education in curriculum.Education)
{
educationSection.Add(new Paragraph($"• {education.Institution}, {education.City}, {education.State}")
.AddStyle(bulletStyle)
.SetFont(bold));
educationSection.Add(new Paragraph($" o {education.Course}")
.AddStyle(subBulletStyle));
educationSection.Add(new Paragraph($" [{education.Status}, {education.Period}]")
.AddStyle(subBulletStyle));
}
document.Add(educationSection);

Div experienceSection = new Div().SetKeepTogether(true);
experienceSection.Add(new Paragraph("Experiência Profissional:").AddStyle(sectionTitleStyle));
foreach (var experience in curriculum.Experience)
{
experienceSection.Add(new Paragraph($"• {experience.Company}, {experience.City}")
.AddStyle(bulletStyle)
.SetFont(bold));
experienceSection.Add(new Paragraph($" o {experience.Position}")
.AddStyle(subBulletStyle));
experienceSection.Add(new Paragraph($" [{experience.Period}]")
.AddStyle(subBulletStyle));

foreach (var descricao in experience.Description)
.Add(new Link("LinkedIn", PdfAction.CreateURI(curriculum.Contact.LinkedIn)).SetUnderline().SetFontColor(ColorConstants.BLUE).AddStyle(textStyle));
document.Add(links);
#endregion

Div educationSection = new Div().SetKeepTogether(true);

#region Certificações
educationSection.Add(new Paragraph("Educação:").AddStyle(sectionTitleStyle));
foreach (var education in curriculum.Education)
{
experienceSection.Add(new Paragraph($"• {descricao}").AddStyle(subBulletStyle));
educationSection.Add(new Paragraph($"• {education.Institution}, {education.City}, {education.State}")
.AddStyle(bulletStyle)
.SetFont(bold));
educationSection.Add(new Paragraph($"{education.Course}").AddStyle(subBulletStyle));
educationSection.Add(new Paragraph($"[{education.Status}, {education.Period}]").AddStyle(subBulletStyle));
}
}
document.Add(experienceSection);
document.Add(educationSection);
#endregion

#region Experiência
Div experienceSection = new Div().SetKeepTogether(true);
experienceSection.Add(new Paragraph("Experiência:").AddStyle(sectionTitleStyle));
foreach (var experience in curriculum.Experience)
{
experienceSection.Add(new Paragraph($"• {experience.Company}, {experience.City}")
.AddStyle(bulletStyle)
.SetFont(bold));
experienceSection.Add(new Paragraph($"{experience.Position}").AddStyle(subBulletStyle).SetItalic());
experienceSection.Add(new Paragraph($"[{experience.Period}]").AddStyle(subBulletStyle));

foreach (var descricao in experience.Description)
{
experienceSection.Add(new Paragraph($"• {descricao}").AddStyle(subBulletStyle));
}
}
document.Add(experienceSection);
#endregion

Div certificationSection = new Div().SetKeepTogether(true);
certificationSection.Add(new Paragraph("Certificações:").AddStyle(sectionTitleStyle));
#region Certificações
Div certificationSection = new Div().SetKeepTogether(true);
certificationSection.Add(new Paragraph("Certificações:").AddStyle(sectionTitleStyle));
foreach (var certification in curriculum.Certification)
{
certificationSection.Add(new Paragraph($"• {certification.Name} - {certification.Institution}").AddStyle(bulletStyle));
}
document.Add(certificationSection);
#endregion

foreach (var certification in curriculum.Certification)
{
certificationSection.Add(new Paragraph($"• {certification.Name} - {certification.Institution}").AddStyle(bulletStyle));
document.Close();
return memoryStream.ToArray();
}
document.Add(certificationSection);

document.Close();
return memoryStream.ToArray();
catch (Exception)
{
throw;
}
}
}
11 changes: 5 additions & 6 deletions CV.Domain/Entity/Certification.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
namespace CV.Domain.Entity
namespace CV.Domain.Entity;

public class Certification
{
public class Certification
{
public string Name { get; set; }
public string Institution { get; set; }
}
public string Name { get; set; }
public string Institution { get; set; }
}
10 changes: 10 additions & 0 deletions CV.Domain/Entity/ContactInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace CV.Domain.Entity;

public class ContactInfo
{
public string Address { get; set; }
public string Telephone { get; set; }
public string Email { get; set; }
public string GitHub { get; set; }
public string LinkedIn { get; set; }
}
22 changes: 8 additions & 14 deletions CV.Domain/Entity/Curriculum.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
namespace CV.Domain.Entity
namespace CV.Domain.Entity;

public class Curriculum
{
public class Curriculum
{
public string Name { get; set; }
public string Contact { get; set; }
public string Address { get; set; }
public string Telephone { get; set; }
public string Email { get; set; }
public string GitHub { get; set; }
public string LinkedIn { get; set; }
public List<Education> Education { get; set; }
public List<Experience> Experience { get; set; }
public List<Certification> Certification { get; set; }
}
public string Name { get; set; }
public ContactInfo Contact { get; set; }
public ICollection<Education> Education { get; set; }
public ICollection<Experience> Experience { get; set; }
public ICollection<Certification> Certification { get; set; }
}
19 changes: 9 additions & 10 deletions CV.Domain/Entity/Education.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
namespace CV.Domain.Entity
namespace CV.Domain.Entity;

public class Education
{
public class Education
{
public string Institution { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Course { get; set; }
public string Status { get; set; }
public string Period { get; set; }
}
public string Institution { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Course { get; set; }
public string Status { get; set; }
public string Period { get; set; }
}
17 changes: 8 additions & 9 deletions CV.Domain/Entity/Experience.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
namespace CV.Domain.Entity
namespace CV.Domain.Entity;

public class Experience
{
public class Experience
{
public string Company { get; set; }
public string City { get; set; }
public string Position { get; set; }
public string Period { get; set; }
public List<string> Description { get; set; }
}
public string Company { get; set; }
public string City { get; set; }
public string Position { get; set; }
public string Period { get; set; }
public ICollection<string> Description { get; set; }
}
Loading

0 comments on commit 4979921

Please sign in to comment.