In my project I have an class named Precatorio
public class Precatorio : Pesquisa.Pesquisa
{
public int FornecedorId { get; set; }
public Fornecedor Fornecedor { get; set; }
public int FundoId { get; set; }
public Fundo Fundo { get; set; }
public int DestinacaoRecursoId { get; set; }
public DestinacaoRecurso DestinacaoRecurso { get; set; }
public DateTime Data { get; set; }
public int Natureza { get; set; }
public long Tipo { get; set; }
public decimal Valor { get; set; }
public string Descricao { get; set; }
private Precatorio()
{
}
internal Precatorio(
int fornecedorId,
Fornecedor fornecedor,
int fundoId,
Fundo fundo,
int destinacaoRecursoId,
DestinacaoRecurso destinacaoRecurso,
DateTime data,
int natureza,
long tipo,
decimal valor,
string descricao
)
{
FornecedorId = fornecedorId;
Fornecedor = fornecedor;
FundoId = fundoId;
Fundo = fundo;
DestinacaoRecursoId = destinacaoRecursoId;
DestinacaoRecurso = destinacaoRecurso;
Data = data;
Natureza = natureza;
Tipo = tipo;
Valor = valor;
Descricao = descricao;
}
}
And a PrecatorioManager class which validates the fields in order to return an object
public class PrecatorioManager : DomainService
{
private readonly IPrecatorioRepository _precatorioRepository;
public PrecatorioManager(IPrecatorioRepository precatorioRepository)
{
_precatorioRepository = precatorioRepository;
}
public async Task<Precatorio> CreateAsync(
int fornecedorId,
Fornecedor fornecedor,
int fundoId,
Fundo fundo,
int destinacaoRecursoId,
DestinacaoRecurso destinacaoRecurso,
DateTime data,
int natureza,
long tipo,
decimal valor,
string descricao
)
{
return new Precatorio(
fornecedorId,
fornecedor,
fundoId,
fundo,
destinacaoRecursoId,
destinacaoRecurso,
data,
natureza,
tipo,
valor,
descricao
);
}
}
The problem is that PrecatorioManager.CreateAsync require to many fields, and it will be too much difficult to change some field and refactor in all the code that use this method. Second problem is that sometimes a developer may not use the PrecatorioManager.CreateAsync and pass throw de validation.Validations needs to pass in PrecatorioManager because some validations require repository injection and will be needed in all cases. Developers commonly use just:
var NewPrecatorio = ObjectMapper.Map<CreateUpdatePrecatorioDto,Precatorio>(input);
So, I'm looking for a way that internal Precatorio calls PrecatorioManager.CreateAsync or a best solution.
As I understand it, the PrecatorioManager
class is responsible for validating new Precatorio
objects and you have three design objectives:
The only way to create a Precatorio
object is to call PrecatorioManager.CreateAsync()
to obtain one.
The developer must not be allowed to bypass the validation of Precatorio
.
The pass-through initializer must be robust and expandable for the future when additional properties may be added to the Precatorio
class.
Similar to the other answer so far, this Precatorio
has a private
constructor that prevents direct instantiation. The static Create
method has internal
visibility to give access to PrecatorioManager
, but checks the [CallerMemberName] attribute and provides "some" assurance by throwing an exception if not being called from a method named CreateAsync
.
One possibility for simplifying the arguments PrecatorioManager.CreateAsync()
would be using a CollectionInitializer similar to the way many HTTP requests are formed.
internal static Precatorio? Create(
Dictionary<string, object> initializer,
[CallerMemberName] string? caller = null)
{
if (caller == "CreateAsync")
{
var created = new Precatorio();
foreach (var kvp in initializer)
{
PropertyInfo? pi = typeof(Precatorio).GetProperty(kvp.Key);
if (pi == null)
{
System.Diagnostics.Debug.Assert(
false,
$"Expecting a property named {kvp}");
}
else
{
pi.SetValue(created, kvp.Value);
}
}
return created;
}
else throw new InvalidOperationException(
$"Precatorio.Create cannot be called from {caller}.");
}
The PrecatorioManager
class is now greatly simplified:
internal class PrecatorioManager
{
private PrecatorioRepository precatorioRepository;
public PrecatorioManager(PrecatorioRepository precatorioRepository) =>
this.precatorioRepository = precatorioRepository;
internal async Task<Precatorio?> CreateAsync(Dictionary<string, object> init)
{
var created = Precatorio.Create(init);
await ValidateAsync(created);
return created;
}
private async Task<bool> ValidateAsync(Precatorio? created)
{
if(created == null) return false;
Console.WriteLine("VALIDATING!");
await Task.Delay(100); // It's up to you what is being awaited here.
return true; // For example, return pass/fail result
}
}
TESTBENCH
Attempting to instantiate Precatorio
won't even compile:
// Illegal attempt to instantiate Precatorio();
var doesNotCompile = new Precatorio();
And attempting to bypass the validation would result in an exception being thrown:
// Illegal attempt to create Precatorio directly
try
{
var notAllowed = await Precatorio.Create(
initializer: new Dictionary<string, object>
{
{ "FornecedorId", 123 },
{ "FundoId", 456 },
{ "DestinacaoRecursoId", 798},
{ "Data", DateTime.UtcNow },
{ "Tipo", 0xeeeeffff },
{ "Valor", 1.234m },
{ "Descricao", "This is a test" },
});
}
catch (Exception ex)
{
Console.WriteLine($"{ex.Message}{Environment.NewLine}");
}
A properly formed call will succeed. Console message confirms validation has taken place:
// Normal create through manager
var precatorio = await manager.CreateAsync(
init: new Dictionary<string, object>
{
{ "FornecedorId", 123 },
{ "FundoId", 456 },
{ "DestinacaoRecursoId", 798},
{ "Data", DateTime.UtcNow },
{ "Tipo", 0xeeeeffff },
{ "Valor", 1.234m },
{ "Descricao", "This is a test" },
});
Console.WriteLine(precatorio?.ToString());
The console printout from the instantiation of a validated Precatorio
object: