Stiamo iniziando un progetto greenfield in ASP.NET MVC. Ho utilizzato il pattern MVC in altri stack (PHP e Ruby) e continuo a incontrare gli stessi problemi su dove posizionare la logica aziendale, la logica dell'interfaccia utente e dove questo sistema interagisce con altri sistemi remoti (ad esempio servizi Web ed e- mail).
Stavo leggendo Vantaggi di ViewModel in MVC e l'autore afferma che ci sono 5 tipi di "logica" in un'applicazione:
Business Logic – Is a part of Model
Database Logic – This is the one of the non-spoken layer in MVC. Many people create a separate layer for this and invoke them from Business layer (model) or some keep them inside Model.
User Interaction logic – written inside controller
Presentation Logic – Presentation logic is the logic which will handle the presentation of a UI. Example – If some value is greater than 100 it should be displayed in green color. Basically it is a part of View.
Data transformation logic – In some situations we have some data with us and we want to display it in a different format. Example we have Date-of-birth and we want to display Age. Again this transformation becomes the part of View.
Vorrei aggiungere un ulteriore "tipo di logica" che è:
- Interfaccia utente / Logica di manipolazione del modello di dominio - Come trasferire dati dalla vista a chiamate di dati e metodi nel modello di dominio
Credo che i primi 5 tipi di logica abbiano già definito le aree. Business Logic entra nei modelli di dominio. La logica del database va nei repository o nel livello O / RM. La logica di interazione dell'utente va nel controller. La logica di presentazione va nel modello di vista. La logica della trasformazione dei dati entra essenzialmente nelle classi che formattano i dati, come date e numeri.
L'ultimo tipo di logica, in cui è necessario prendere i dati dalla vista e manipolare il modello di dominio di conseguenza, non sembra avere il proprio posto.
Ho letto circa due possibilità:
-
Un "livello di servizio" nell'applicazione, anche se non viene fatto alcun riferimento ai servizi Web, ma usando "oggetti di servizio" per incapsulare questo
-
Un oggetto "unità di lavoro" - Questo è stato particolarmente frustrante perché una transazione di database è un'implementazione del modello Unità di lavoro. Le recenti variazioni di questo modello aumentano la transazione del database con le manipolazioni effettive del modello di dominio.
-
Oggetto Action / Executor ( Pattern Action / Executor )
-
Oggetti di comando (modello di comando)
Creare un nuovo post sul blog è facile: inserisci il post nel database. La creazione di un nuovo utente non è così semplice, perché potresti voler inviare un'e-mail di benvenuto dopo aver inserito l'utente nel database, quindi le operazioni aggiuntive vengono eseguite dopo che "l'unità di lavoro" è stata completata.
Un piccolo esempio di codice.
Diciamo che abbiamo un'applicazione per il blog.
- Un blog ha molti post
- Un post del blog non può esistere senza un blog
- Un post sul blog deve avere un titolo, un corpo e una data di pubblicazione
Questo porta a due Classi di Dominio: Blog e BlogPost. Per evitare di creare accidentalmente un oggetto BlogPost e aggiungerlo al Blog errato, il costruttore della classe BlogPost è contrassegnato come internal
, quindi sei obbligato ad aggiungere un post del blog attraverso l'oggetto Blog a cui appartiene:
public class Blog
{
public Blog(string name)
{
Name = name;
posts = new List<BlogPost>();
}
public int Id { get; internal set; }
public string Name { get; private set; }
public string Description { get; set; }
private IList<BlogPost> posts;
// Return a read-only collection of posts to the public
public IEnumerable<BlogPost> Posts
{
get { return posts; }
}
// Create a new BlogPost object to ensure it is attached to the proper Blog
public BlogPost AddBlogPost(string title, string body, DateTime publishDate)
{
BlogPost post = new BlogPost(this, title, body, publishDate);
posts.Add(post);
return post;
}
}
E la classe BlogPost:
public class BlogPost
{
// This constructor is "internal" so code outside this Assembly must call
// Blog.AddBlogPost(...) to create new posts
internal BlogPost(Blog blog, string title, string body, DateTime publishDate)
{
Blog = blog;
SetTitleAndBody(title, body);
PublishDate = publishDate;
}
public int Id { get; internal set; }
public string Title { get; private set; }
public string Body { get; private set; }
public Blog Blog { get; private set; }
public DateTime PublishDate { get; private set; }
public int BlogId
{
get { return Blog.Id; }
}
public bool IsPublished
{
get { return PublishDate < DateTime.Now; }
}
public bool IsUnpublished
{
get { return !IsPublished; }
}
public void PublishAt(DateTime newPublishDate)
{
PublishDate = publishDate;
}
public void SetTitleAndBody(string title, string body)
{
if (string.IsNullOrEmpty(title))
throw new ArgumentNullException("title");
if (string.IsNullOrEmpty(body))
throw new ArgumentNullException("body");
Title = title;
Body = body;
}
}
Il modello di visualizzazione per un post del blog potrebbe essere simile a:
public class BlogPostForm
{
public BlogPostForm()
{
}
public BlogPostForm(BlogPost post)
{
Id = post.Id;
Title = post.Title;
Body = post.Body;
PublishDate = post.PublishDate;
BlogId = post.BlogId;
}
[Required]
public int Id { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string Body { get; set; }
[Required]
public DateTime? PublishDate { get; set; }
[Required]
public int BlogId { get; set; }
}
Si noti che PublishDate è un valore DateTime nullable poiché l'utente potrebbe inviare il modulo senza riempire la data di pubblicazione. Inoltre, a differenza del modello di dominio, tutte le proprietà sono get / set pubblici, anche perché l'utente potrebbe inviare il modulo senza riempire quei campi.
Ora qualcosa deve prendere i dati nell'oggetto BlogPostForm
e trovare Blog
nel database e chiamare AddBlogPost
:
IBlogRepository blogs = ...
Blog blog = blogs.Find(model.BlogId);
BlogPost post = blog.AddBlogPost(model.Title, model.Body, model.PublishDate.Value);
blogs.Save(blog);
Dove dovremmo mettere la logica su come manipolare gli oggetti Blog e BlogPost in base ai valori nell'oggetto BlogPostForm, specialmente se questo lavoro include più di COMMIT
in un database?