How would you organize a large complex web application (see basic example)?

自古美人都是妖i 提交于 2019-11-29 19:34:45
Xian

The way in which I would do this is 3 fold.

  1. Encapsulate javascript in small well-defined classes within namespaces
  2. Javascript classes should have HTML they require "injected" into them as dependency allowing out-of-browser unit testing
  3. Move as much client-side functionality to server as possible and use a concept known as AHAH

Javascript name-spacing

This can be achieved easily and has been covered in other posts such as this Is there a "concise" way to do namespacing in JavaScript?

Small encapsulated classes

Javascript code, just like server-side code should be well encapsulated with small cohesive classes and methods. Each class lives in a separate file, named along with the namespace it is in, eg: MyCompany.SomePackage.MyClass.js. Excessive HTTP requests to each file can be saved via combining and minifying these class files at build time.

Dependency Inversion in Javascript

So effectively instead of selecting the elements you require to work with inside your class, like this:

var MyNamespace.MyClass = function() {
  var elementINeed = $('#IdOfElementINeed');
}

You would inject it as such:

var foo = new MyNamspace.MyClass($('#IdOfElementINeed'));

var MyNamespace.MyClass = function(incomingDependency) {
  var elementINeed = incomingDependency;
}

This technique lends itself well to testable javscript and seperation of concerns through MVC style layering of your code.

AHAH and Client-side simplification

AHAH is quite an old technique that has been around for quite some time in web-development, although is making a resurgence amongst web aficionados for its pure simplicity. However, the philosophy must be bought into at more than the architectural technique level and it must be used as a replacement for all your client side javascript eg: validation, showing/hiding dynamic content, calculations etc

Where you may used to have attached an onClick event with client-side complexity:

$('#someElement').click(function(){
  // insert complex client-side functionality here, such as validating input
  // eg var isValid = $(this).val() < minimumCommentLength;
  // update page based on result of javascript code
  // eg $('#commentTooLong').show();
})

Now you would simply trigger an ajax request back to the server to get the new HTML and simply replace all or some of the elements you are interested in as such:

$('#addCommentButton').click(function(){
  $.ajax({ 
    url: "/comment/add", 
    context: document.body, success: 
    function(responseHTML){
        $('body').html(reponseHTML);
      }});
})

Obviously this is a trivial example, but when used effectively, ANY javascript event on the page, simply fires off the identical ajax request and HTML replacement, greatly reducing the amount of client-side code required. Moving it to the server where it can be effectively tested.

AHAH nay-sayers, will argue that this is not a performant way to run a web-site, however I have used and seen this technique on sites with 56k modem access and also massively scaled public web-sites. The result is of course slower, but you can still produce sub 100 millisecond round trips, which is practically instant to humans.

Matpol gave a literal solution to the specific information provided. Your edit implies that you are looking for an answer to a more hypothetical question. In other words, you're looking for the "approach".

Answering the question in this manner; the single example you gave is a bit of a red herring. It is but a single item in the whole app, and prevents us from "seeing the forest from the trees". So what is the Forest? The question, asked as such, doesn't define it. But I think all programmers will agree when I say it's a nightmare to work on a project that has no definition. So, I'll rephrase your question and answer, "What is the process for defining a project?"

Mine is, in fact, asking a series of questions:

  • What is the core purpose of this application?
  • When do you want it launched? If we had to launch it in 1/4 that time, which features would you keep?
  • Which features are you absolutely certain you're going to need afterwards?

A lot of the time, to get to the bottom of those questions, I need to ask other business questions:

  • Who is your audience?
  • Why do they care about this site? What's going to keep them coming back?
  • How are you going to generate revenue?
  • What is your call to action? If you could funnel all of your users down a single path, which would it be?

Hopefully, these questions will result in a set of foundational code that you can consider your core. Your core, as you suspected, probably doesn't fit the modular approach. And as you identified, you will want to break that core down into a Model / View / Controller layout.

But it's inevitable that you will need to add design fluff. And this brings us back to your code example - its fluff. Fluff belongs in a plugin, separated from the core. This isn't to say that all of your plugins should be delivered to the user in separate js files... but for your development purposes, they should be viewed as modular, and independent from the core code base.

I would either turn it in to a jQuery plugin or a static object.

The static object just acts as a kind or wrapper. I would also break it up in to smaller functions e.g.

init()
checkLength()
checkTime()

So you might end up with something like:

Widget = {

init:function(){//setup events etc},
checkLength:function(){},
checkTime:function(){},
doMessage:function(){}


}

The current code is a good start and can be made to "scale" into large apps that avoid hard-coding and have clear MVC separation with just a few changes.

  • This should ideally be a widget or some sort of packaged functionality

A widget will make reusing comment functionality easier and provide for reuse in different pages/apps. Extend the encapsulation and separation of concerns not only to the presentation, but also to the widget model. When you think of a comment field, it's intuitive to think of the state of the component as the comment text, yet all the parameters affecting it's behaviour can be part of it's model, including validation parameters. So, in addition to the comment text, I would have the model include:

  • a mapping of character count to a size-category (too-small, small, medium, large, overflow).
  • the max comment submit frequency (15 seconds)

The widget model updates the size-category as the text is changed. The view listens to changes to the size-category, and the size-category value used to update the text class to produce CSS-styling for different comment lengths. The size-category is also checked when "Add Comment" is clicked. If it is "too-small" or "overflow" then a popup can be shown. Note that the Add Comment handler does not checking character count - that is isolated in the model - it checks the size-category. If necessary, the "Add Comment" controller could use the same mapping of character count to size-category that the model uses to produce helpful messages for the user. E.g. "Comments must be at least 15 characters" where 15 is fetched from the size-category map.

The model also provides a "number of characters left" property whose change events are used to update the widget's UI. The maximum character count is fetched from the character to size-category map.

  • Things like a comment per 15 seconds, and minimum 15 character comment belong to some application wide policies rather than being embedded inside each widget.
  • Too many hard-coded values.
  • No code organization. Model, Views, Controllers are all bundled together. Not that MVC is the only approach for organizing rich client side web applications, but there is none in this example.

There may be many types of comments (e.g. different items being commented on) and different types of comment-widget. If they should all have the same minimum/maximum range then they should all be parameterized with the same model values controlling the comment validation. This is best done server-side when building the data for the comment model. The comment text is fetched for that specific comment, and the comment validation values, such as size-category mapping are fetched from page or application configuration defaults. By having central logic for producing the component validation model, adding new rules becomes much simpler - e.g. such as "moderators can post comments upto 1K", becomes a change in one piece of code. Another point to having the component model computed server side is that the model should also be validated server-side - client validation is more a user-convenience (inconvenience some might think!) - and not a hard enforcement. JavaScript can be disabled and HTTP requests constructed independently from the validating client.

To sum up, much of this can be seen as organizing production of the widget model server side. By doing this server side, the server can enforce validation rules and shield the widget from the complexity of the rules and application-wide configuration.

I've not mentioned jQuery or any UI technogy since this pattern is valid for applications irrespective of UI technology. How you apply the pattern will be UI-specific to some extent such as how the validation model is provided to the widget, or how to hook up listeners to the model, but the organizational level of the pattern is orthogonal to the UI. The primary focus is on the model - extending it to include validation aspects and computing it server side. Once that is in place, the organization problem is pretty much solved.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!