La "convalida" di solito si applica a dati che sono fuori controllo, più comunemente dati immessi da un utente finale, ma in alcune situazioni è possibile convalidare anche i dati di back-end se vengono recuperati da un servizio di terze parti. Sarebbe inusuale "convalidare" i dati che controlli.
Ecco come vorrei refactoring questo:
-
Le informazioni sul richiedente come nome e data di nascita sarebbero convalidate tramite script lato client per fornire un feedback immediato.
-
La convalida sul lato client sarebbe ripetuta sul lato server per rilevare eventuali manipolazioni di dati che potrebbero essere eseguite con Javascript disattivato o tramite acquisizione / modifica della richiesta utilizzando uno strumento come Paros. Mentre è "pulito" per mettere quel tipo di convalida nel livello aziendale, la realtà è che spesso è conveniente farlo nel livello di presentazione, poiché ci sono strumenti che consentono di replicare la convalida di JavaScript con la convalida lato server ( ad es. Page.IsValid ) . Ma potresti farlo in entrambi i posti.
-
Una volta che il richiedente è stato completamente convalidato, il sito chiede al livello aziendale di produrre un elenco di offerte valide per l'utente. Sarebbe meglio generare queste offerte endogenamente ... in altre parole, prendere i criteri del richiedente come input e quindi produrre la lista. Scorrere l'elenco completo di tutte le offerte possibili e convalidarle una per una sarebbe meno efficiente, soprattutto se il sistema contiene molte offerte per le quali l'utente non è idoneo.
Per ottenere l'elenco delle offerte, il modo più ovvio sarebbe con una chiamata simile a questa:
List<Offer> offers = offerFactory.GetOffers(applicant.DateOfBirth,
applicant.OtherCriteria,
applicant.Etc);
Tuttavia, se ci sono un sacco di campi, potresti essere tentato di passare invece l'intero richiedente, in questo modo:
List<Offer> offers = offerFactory.GetOffers(applicant);
Ciò solleva la domanda su quale input si aspetta il metodo GetOffers
. Potrebbe aspettarsi un Applicant
, suppongo, come questo:
class OfferFactory
{
public List<Offer> GetOffers(Applicant applicant)
{
//Code goes here
}
}
Ma ciò crea una dipendenza dalla classe Applicant
concreta. Potrebbe essere meglio passare un'interfaccia. Possiamo interpretare ogni candidato con criteri di applicazione, quindi possiamo definire la classe Richiedente con un'interfaccia come questa:
public interface IApplicationCriteria
{
DateTime DateOfBirth;
}
class Applicant: IApplicationCriteria
{
public DateTime DateOfBirth { get; set; }
public string FirstName { get; set; } //Not in the interface
public string LastName { get; set; } //Not in the interface
}
Quindi la nostra implementazione GetOffers()
accetta l'interfaccia che contiene solo i dati di cui ha bisogno:
public List<Offer> GetOffers(IApplicationCriteria criteria)
{
//Code goes here
}
Ma lo chiameresti comunque allo stesso modo:
List<Offer> offers = offerFactory.GetOffers(applicant);
Tuttavia, passando solo l'interfaccia, creiamo una migliore separazione delle preoccupazioni. Ciò potrebbe essere utile se, ad esempio, un giorno decidiamo che un Applicant
non ha FirstName
e LastName
, ma invece ha FullName
. La chiamata di GetOffers()
non dovrebbe interessare, e possiamo dire che non gli interessa al momento della compilazione.
Nel frattempo, GetOffers()
utilizza i criteri per generare una query nella tabella delle offerte
class OfferFactory
{
private readonly SqlConnection _connection;
public OfferFactory(SqlConnection connection)
{
_connection = connection; //Injected
}
public List<Offer> GetOffers(IApplicationCriteria criteria)
{
var offers = new List<Offer>();
SqlCommand cmd = new SqlCommand("GetOffers");
cmd.CommandType = CommandType.StoredProcedure;
cmd.AddParameterWithvalue("@DateOfBirth", criteria.DateOfBirth);
cmd.Connection = _connection;
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
offers.Add(new Offer {
OfferID = reader["OfferID"] as string,
OfferName = reader["OfferName"] as string,
Price = reader["Price"] as decimal
}
);
}
return offers;
}
}
In questo modo non chiami Convalida ripetutamente su dati che sai essere già appropriati per il richiedente.
Se davvero volevi creare il maggior numero di livelli possibile, puoi spostare l'accesso ai dati su un altro livello, ma in un caso come questo, dove stai selezionando un sottoinsieme di dati, sarebbe preferibile mantenere livello aziendale "stupido" e lascia che il database trovi i record per te; solo ha gli indici e i dati necessari per trovare le offerte giuste senza guardare tutte le offerte che non sono valide.
Come apparirebbe lo sproc? Dipende dalla struttura dei dati, ma potrebbe andare lungo le linee di questo:
CREATE PROC GetOffers(@DateOfBirth DateTime)
AS
BEGIN
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
DECLARE @Age int
DECLARE @OfferCount int
--Compute the applicant's age
SELECT @Age = DATEDIFF(hour, @dateOfBirth, GETDATE())/8766.0
--Obtain the number of offers allowed
SELECT @OfferCount = MAX(OfferCount)
FROM OfferAgeRules
WHERE MinAge <= @Age
AND MaxAge >= @Age
--Get the offers
SELECT TOP (@OfferCount)
o.OfferID,
o.OfferName,
o.Price
FROM Offers o
WHERE o.MaxAge >= @Age
AND o.MinAge <= @Age
AND o.OfferStartDate < GETDATE()
AND o.OfferGoodUntil > GETDATE()
ORDER BY o.Priority
END