What are your best practices when using an MVC-bas

2019-04-16 00:47发布

问题:

A few general questions to those who are well-versed in developing web-based applications.

Question 1:

How do you avoid the problem of "dependency carrying"? From what I understand, the first point of object retrieval should happen in your controller's action method. From there, you can use a variety of models, classes, services, and components that can require certain objects.

How do you avoid the need to pass an object to another just because an object it uses requires it? I'd like to avoid going to the database/cache to get the data again, but I also don't want to create functions that require a ton of parameters. Should the controller action be the place where you create every object that you'll eventually need for the request?

Question 2:

What data do you store in the session? My understanding is that you should generally only store things like user id, email address, name, and access permissions.

What if you have data that needs to be analyzed for every request when a user is logged in? Should you store the entire user object in the cache versus the session?

Question 3:

Do you place your data-retrieval methods in the model itself or in a separate object that gets the data and returns a model? What are the advantages to this approach?

Question 4:

If your site is driven by a user id, how do you unit test your code base? Is this why you should have all of your data-retrieval methods in a centralized place so you can override it in your unit tests?

Question 5:

Generally speaking, do you unit test your controllers? I have heard many say that it's a difficult and even a bad practice. What is your opinion of it? What exactly do you test within your controllers?

Any other tidbits of information that you'd like to share regarding best practices are welcome! I'm always willing to learn more.

回答1:

  • How do you avoid the problem of "dependency carrying"?

Good object oriented design of a BaseController SuperClass can handle a lot of the heavy lifting of instantiating commonly used objects etc. Usage of Composite types to share data across calls is a not so uncommon practice. E.g. creating some Context Object unique to your application within the Controller to share information among processes isn't a terrible idea.

  • What data do you store in the session?

As few things as is humanly possible.

If there is some data intensive operation which requires a lot of overhead to process AND it's required quite often by the application, it is a suitable candidate for session storage. And yes, storage of information such as User Id and other personalization information is not a bad practice for session state. Generally though the usage of cookies is the preferred method for personalization. Always remember though to never, ever, trust the content of cookies e.g. properly validate what's read before trusting it.

  • Do you place your data-retrieval methods in the model itself or in a separate object that gets the data and returns a model?

I prefer to use the Repository pattern for my models. The model itself usually contains simple business rule validations etc while the Repository hits a Business Object for results and transformations/manipulations. There are a lot of Patterns and ORM tools out in the market and this is a heavily debated topic so it sometimes just comes down to familiarity with tools etc...

  • What are the advantages to this approach?

The advantage I see with the Repository Pattern is the dumber your models are, the easier they are to modify. If they are representatives of a Business Object (such as a web service or data table), changes to those underlying objects is sufficiently abstracted from the presentation logic that is my MVC application. If I implement all the logic to load the model within the model itself, I am kind of violating a separation of concerns pattern. Again though, this is all very subjective.

  • If your site is driven by a user id, how do you unit test your code base?

It is highly advised to use Dependency Injection whenever possible in code. Some IoC Containers take care of this rather efficiently and once understood greatly improve your overall architecture and design. That being said, the user context itself should be implemented via some form of known interface that can then be "mocked" in your application. You can then, in your test harness, mock any user you wish and all dependent objects won't know the difference because they will be simply looking at an interface.

  • Generally speaking, do you unit test your controllers?

Absolutely. Since controllers are expected to return known content-types, with the proper testing tools we can use practices to mock the HttpContext information, call the Action Method and view the results to see they match our expectations. Sometimes this results in looking only for HTTP status codes when the result is some massive HTML document, but in the cases of a JSON response we can readily see that the action method is returning all scenario's information as expected

  • What exactly do you test within your controllers?

Any and all publicly declared members of your controller should be tested thoroughly.

Long question, longer answer. Hope this helps anyone and please just take this all as my own opinion. A lot of these questions are religious debates and you're always safe just practicing proper Object Oriented Design, SOLID, Interface Programming, DRY etc...



回答2:

Regarding dependency explosion, the book Dependency Injection in .NET (which is excellent) explains that too many dependencies reveals that your controller is taking on too much responsibility, i.e. is violating the single responsibility principle. Some of that responsibility should be abstracted behind aggregates that perform multiple operations.

Basically, your controller should be dumb. If it needs that many dependencies to do its job, it's doing too much! It should just take user input (e.g. URLs, query strings, or POST data) and pass along that data, in the appropriate format, to your service layer.

Example, drawn from the book

We start with an OrderService with dependencies on OrderRepository, IMessageService, IBillingSystem, IInventoryManagement, and ILocationService. It's not a controller, but the same principle applies.

We notice that ILocationService and IInventoryManagement are both really implementation details of an order fulfillment algorithm (use the location service to find the closest warehouse, then manage its inventory). So we abstract them into IOrderFulfillment, and a concrete implementation LocationOrderFulfillment that uses IInventoryManagement and ILocationService. This is cool, because we have hidden some details away from our OrderService and furthermore brought to light an important domain concept: order fulfillment. We could implement this domain concept in a non-location-based way now, without having to change OrderService, since it only depends on the interface.

Next we notice that IMessageService, IBillingSystem, and our new IOrderFulfillment abstractions are all really used in the same way: they are notified about the order. So we create an INotificationService, and make MessageNotification a concrete implementation of both INotificationService and IMessageService. Similarly for BillingNotification and OrderFulfillmentNotification.

Now here's the trick: we create a new CompositeNotificationService, which derives from INotificationService and delegates to various "child" INotificationService instances. The concrete instance we use to solve our original problem will delegate in particular to MessageNotification, BillingNotification, and OrderFulfillmentNotification. But if we wish to notify more systems, we don' have to go edit our controller: we just have to implement our particular CompositeNotificationService differently.

Our OrderService now depends only on OrderRepository and INotificationService, which is much more reasonable! It has two constructor parameters instead of 5, and most importantly, it takes on almost no responsibility for figuring out what to do.