• No se han encontrado resultados

El diseño exterior una tienda de lujo, que transmita la experiencia de marca, aumentan la satisfacción de los clientes

You might wish that you could compile everything once, instead of performing many small compilations. The problem with that, though, is that you run into issues with compilation time when you have large numbers of scripts.

The best solution is a compromise. We want some batching in our compilation, but we don’t want to compile everything at once and pay the high cost of a large compila-tion.

NOTE We’ll assume here that we’re talking about scripts that reside on the file-system. For scripts stored elsewhere, the concepts are similar, but the implementation depends on the concept of hierarchy in the selected storage mechanism.

When we get a request to execute a certain script, we perform the following operations:

1 Check if the script has already been compiled and exists in the cache.

2 If it’s in the cache, instantiate and return the new instance, and we’re done.

3 If it isn’t in the cache, compile all the scripts in the script directory.

4 Register all the scripts in the cache.

5 Instantiate the compiled script and return the new instance.

The key here is in step number 3. Instead of compiling only the script we’re interested in, we compile all the scripts in the current directory and register them in the cache.

This means that we pay the compilation cost once per directory. It also means that we have bigger (and fewer) assemblies. We can now rely on the natural organization of the filesystem to limit the number of scripts in a directory to a reasonable number.

Because we usually place scripts on the filesystem according to some logic, and because we usually access them according to the same logic, this turns out to be a pretty good heuristic to detect which scripts we should compile.

Cache invalidation puts a tiny wrinkle in this pretty scenario, though. When a script changes, we remove it from the cache, but we also note that this is a script that we have already compiled. When a new request comes for this script, we won’t find it in the cache, but we will find it in the list of files that were compiled and then changed. As a result, we won’t perform a batch compilation in this scenario; we’ll compile only the current script. The logic is simple: if we had to recompile the script, we already performed a batch compilation on its directory, so we don’t need to pile the entire directory again. The end result is a tiny assembly that contains the com-piled type from the script that was changed.

This process isn’t my own idea. ASP.NET operates in a similar way when it compiles ASPX files. I used the same ideas when the time came to build the compilation and caching infrastructure of Rhino DSL.

This just about wraps things up for Rhino DSL (it’s a tiny library, remember?). We just have one final topic to cover: handling external dependencies and integration with external factories.

7.5 Supplying external dependencies to our DSL

Although the default approach of creating new instances of the DSL using the default constructor is fine for simple cases, it gets tricky for more complex situations. For complex DSLs, we need access to the application’s services and infrastructure.

In most applications, this is handled by using either a static gateway (a static class that provides the given service) or by using dependency injection (passing the services to the instance using the constructor or settable properties).

The advantage of static gateways is that the DSL can call them. Listing 7.12 shows an Authorization DSL that makes additional calls to the Authorization static gateway to perform its work.

canApprove = Authorization.IsAllowed(Principal, "/order/approve") canDispatch = Authorization.IsAllowed(Principal, "/order/dispatch") if canApprove and canDispatch:

return Allow("Can both approve and dispatch") else:

return Deny("Cannot both approve and dispatch")

This is easy to build, but I dislike this approach. It tends to make testing awkward (we’ll discuss DSL testing in the next chapter). I much prefer to use dependency injection.

Depending on the infrastructure of our application, we have different ways of han-dling external dependencies, but the built-in option is to pass the parameters to the constructor. That’s what we did when we built the Quote-Generation DSL, as you can see in listing 7.13.

QuoteGeneratorRule rule = dslFactory.Create<QuoteGeneratorRule>(url, new RequirementsInformation(200,"Vacation"));

rule.Evaluate();

Listing 7.13 shows the bare-bones approach to dependency injection, but we may want to use a more advanced technique. The advanced options all essentially amount to overriding the DslEngine CreateInstance method and modifying how we create an instance of our DSL.

Listing 7.14 shows how we could create an instance of a DSL by routing its creation through an IoC container (such as Windsor or StructureMap).

public override object CreateInstance(Type type, params object[] parametersForConstructor) {

return container.CreateInstance(type);

}

Listing 7.12 An Authorization DSL script that calls back to the Authorization gateway

Listing 7.13 Passing parameters to the Quote-Generation DSL instance

Listing 7.14 Routing DSL creation through an IoC container

149 Summary

Now all the DSL dependencies can be satisfied by the container, instead of having to be manually supplied.

NOTE The type we created in listing 7.14 was not previously registered in the container (we just compiled it, after all). The IoC container needs to sup-port creating instances of unregistered types, but most of them do.

Note that although it works, directly using the application services from the DSL is an approach you should consider carefully. IoC containers aren’t often written with an eye toward their use in DSLs, and they may allow leakage of programming concerns into the language.

It’s generally better to handle the application services inside the DSL base class, and then to expose methods that properly match the style of the DSL. This also helps significantly when you need to create a new version of the DSL, and you need to change those services. If you have a facade layer, your job is that much easier. We’ll talk about this more in chapter 9.

7.6 Summary

In this chapter, we looked at the requirements of a DSL infrastructure and saw how they’re implemented by Rhino DSL. We looked at caching and batching, and at how those work together to produce a well-performing system.

We explored the extensibility options that Rhino DSL offers and we wrote a DSL engine storage class that could load scripts from an XML file, as an example of how to deal with non-filesystem-based storage (databases, source control, and so on). And last, but not least, we discussed the issue of providing dependencies to our DSL scripts.

This chapter is short, but it provides a thorough grounding in the underlying infrastructure that we build upon, as well as outlining the design requirements that have led to building it in this fashion.

With this knowledge, we can now start managing DSLs in real-world applications.

We’ll look next at how we can integrate our DSLs with test-driven development prac-tices and test both the DSL implementations and the DSLs themselves.

150