The slides & code from my JAOO talk can be found here.
Started the day in a presentation from Robert C. Martin, which was very good, both in content and delivery. Moved on to give a talk about building DSL with Boo. It went well, as far as I can tell, but I was the one giving it.
After that I was at the Erlang talk, which is quite probably the best one that will be here. Joe has created the language and wrote the book about it, so he certainly knows his stuff, and he is a Character with a capital C. I am not sure if it is a show or not, but it was amazingly amusing.
From my point of view, the major advantage of Erlang oer anything else is the ability to wait without blocking an os thread. That is something that I don't think that we can do (externally) on .Net, which is a shame.
Bought the Erlang book, it is a weird language compare to those I know, but I really need to learn a new language this year, and Erlang gets me both functional and concurrent aspects for the "price" of one.
After that I sat in a couple of talks in the Ruby track, "Ruby in the Enterpise" and "Ruby and the art of building DSLs".
I like the ideas being evolved in Ruby, but I have reservation about doing development there. I know that I say this as a person who didn't do any real project there, but I have the feeling that Ruby isn't for me. Basically doesn't provide a significant advantage over what I already have in .Net
The reason that I say that it is not for me is because development is actually a small part of a software project, and there is a lot of other things that I don't think that Ruby does as well as other platforms (profiling, debugging, deployment, monitoring, etc).
To be rather more precise, I think that .Net has much better tooling surrounding it than those for Ruby. That, coupled with the fact that I don't see a Killer Feature of working on Ruby itself, means that I am not really interested in it as a platform.
I am very interested in Ruby as a place to harvest a lot of interesting and efficent approaches. It is certainly one of the fastest moving communities around, and it is rich in smart people.
Right now I am at the Spring.NET talk, wihch is interesting, not because of the technology concepts (I mostly know them), but because of the different design decisions that were made, and the different "culture" that afected it.
Colin Ramsay has a short screen cast showing the advantages of using MonoRail HotSwap. I should mention that the startup times that you see in the screen cast are for empty projects, for real world projects they are much longer.
And yes, that is how I say Ayende. :-)
Allow me to divert a bit from the usual technical rambling of this blog. I have just arrived at JAOO, and I am still in awe of the Copenhagen's air port. Unlike Israel's or Frankfort's airports, which are industrial, commercial and ad-laden, the Copenhagen airport was a work of art.
It is not a really a good way to describe it, but the entire place is beautifully coordinated, and it feels like the stores there are there to complete it, not to shout at me. The entire thing is holistically whole.
The rest of the place is also quite amazing, from the most comfortable chairs in an airport that I have seen to really nice places for kids (from a toy garden to a lego playfield), etc. The attention for beauty goes across the entire (huge) airport, which means that it was really impressive.
Here is an image that I stole from flickr, it doesn't really do it justice, but it should give you the starting of an idea. You really have to be there to be impressed by it.
I am going to leave for JAOO in a few minutes, see you next in Denmark :-)
I hate aimless bitching, and this post annoyed me enough to decide to do something about it. The basic problem is that making any change at all to an ASP.Net application requires an AppDomain load / unload, which takes a lot of time. This means that a quick change and browser refresh are not possible, you are trying to minimize those waits as much as possible, and that hurts the feedback cycle.
MonoRail makes it much easier, because you are not forced to do this if you make any change to the views, but changing the controller can get to be a PITA, because of the wait times.
Problem.
Let me walk you through what I was thinking:
- I need to have a way to get the new version of the controller into the already running application.
- I am using Windsor.
- I had a discussion two weeks ago about Java's hot deployment capabilities.
- Controllers are independent units, for the most part.
So, what does this mean? It means that I can probably take any single controller and recompile it independently of the rest of the application. As a direct result of that, I can register the new controller version in Windsor, so the next time MonoRail needs this controller, it will get the new version.
The rest was just a matter of runtime compilation...
The code is listed below, I was astonished to discover that this is so freaking easy!
The proof of concept code is 70 lines, and it allows to make any change to a controller and it will be immediately be reflected in the application.
Now, why is it proof of concept?
- It doesn't really handles errors correctly, it just removes the controller if it was changed and that is it. You probably want to get an error page with the compilation errors.
- It will recompile the minute that you are saving, you probably want to recompile on the first request.
- You may want to recompile the entire application if the first compile did not succeed, that would allow you to handle more than a single change. That is fairly complex thing to do, though.
The first two issues can be fairly easily solved using a SubDependencyResolver on Windsor, which will intercept the incoming calls for controllers' instances. It would then try to compile the controller if it was changed, and throw an exception if it cannot compile.
Important: I am going to leave to JAOO in a few hours, so I won't have time to follow up on this as it deserves. I have every confidence that William will :-)
Here is the code:
namespace Castle.MonoRail.Framework { using System; using System.CodeDom.Compiler; using System.IO; using System.Reflection; using Microsoft.CSharp; using Windsor; public class HotSwap { private readonly string directoryToWatch; private readonly IWindsorContainer container; private readonly Assembly assembly; private readonly string controllersNamespace; public HotSwap(string directoryToWatch, IWindsorContainer container, Assembly assembly, string controllersNamespace) { this.directoryToWatch = directoryToWatch; this.container = container; this.assembly = assembly; this.controllersNamespace = controllersNamespace; } public void Start() { FileSystemWatcher watcher = new FileSystemWatcher(directoryToWatch, "*.cs"); watcher.Created += CodeChanged; watcher.Changed += CodeChanged; watcher.Renamed += CodeChanged; watcher.EnableRaisingEvents = true; } void CodeChanged(object sender, FileSystemEventArgs e) { string fileName = Path.GetFileNameWithoutExtension(e.FullPath); string typeName = controllersNamespace+"."+fileName; CompilerParameters options = CreateCompilerOptions(); CSharpCodeProvider provider = new CSharpCodeProvider(); CompilerResults compilerResults = provider .CompileAssemblyFromFile(options, e.FullPath); container.Kernel.RemoveComponent(typeName); if(compilerResults.Errors.HasErrors) return; Type type = compilerResults.CompiledAssembly.GetType(typeName); container.AddComponent(type.FullName, type); } private CompilerParameters CreateCompilerOptions() { CompilerParameters options = new CompilerParameters(); options.GenerateInMemory = true; options.GenerateExecutable = false; options.ReferencedAssemblies.Add(assembly.Location); foreach (AssemblyName name in assembly.GetReferencedAssemblies()) { Assembly loaded = Assembly.Load(name.FullName); options.ReferencedAssemblies.Add(loaded.Location); } options.IncludeDebugInformation = true; return options; } } }
And in the global.asax:
public class GlobalApplication : UnitOfWorkApplication { public override void Init() { HotSwap swap = new HotSwap(directory, Container, Assembly.GetExecutingAssembly(), "MyApp.Controllers"); swap.Start(); } }
I hate XML, a long time ago, I also hated XML, but I also had some free time, and I played with building a build system in Boo. To match NAnt, I called it NUncle.
It never really gotten anywhere, but Georges Benatti has taken the code and created the Boo Build System. I am just taking a look, and it is fairly impressive. It has the concept of tasks and dependencies between them, as well as action that it can perform.
Here is a part of Boobs' own build script:
Task "build boobs", ["build engine", "build extensions"]: bc = Booc( SourcesSet : FileSet("tools/boobs/**/*.boo"), OutputFile : "build/boobs.exe" ) bc.ReferencesSet.Include("build/boobs.engine.dll") bc.ReferencesSet.Include("build/boo.lang.useful.dll") bc.Execute() Task "build engine": Booc( SourcesSet : FileSet("src/boobs.engine/**/*.boo"), OutputFile : "build/boobs.engine.dll", OutputTarget: TargetType.Library ).Execute() Task "build extensions", ["build io.extensions", "build compiler.extensions"] Task "build io.extensions": Booc( SourcesSet : FileSet("src/extensions/boobs.io.extensions/**/*.boo"), OutputFile : "build/boobs.io.extensions.dll", OutputTarget: TargetType.Library ).Execute() Task "build compiler.extensions": bc = Booc( SourcesSet : FileSet("src/extensions/boobs.compiler.extensions/**/*.boo"), OutputFile : "build/boobs.compiler.extensions.dll", OutputTarget : TargetType.Library ) bc.ReferencesSet.Include("build/boobs.io.extensions.dll") bc.Execute()
I don't know about you, but this makes me feel very nice.
The concept is pretty obvious, I feel, and the really nice thing is that extending it is a piece of cake. Here is how you validate dates of two files:
def IsUpToDate(target as string, source as string): return true unless File.Exists(source) return false unless File.Exists(target) targetInfo = FileInfo(target) sourceInfo = FileInfo(source) return targetInfo.LastAccessTimeUtc >= sourceInfo.LastAccessTimeUtc
And its usage:
Cp("source.big", "dest.big") if not IsUpToDate("source.big", "dest.big")
Or, you know what, this is fairly routine, and it comes as part of the standard library for Boobs. Let us create something new, ConditionalCopy:
def ConditionalCp(src as string, dest as string): Cp(src, dest) if not IsUpToDate(src, dest)
Usage should be clear by now, I hope.
Boo has gotten a lot better in terms of flexibility lately, a lot better. I was able to churn this little DSL in about an hour:
OnCreate Account: Entity.AccountNumber = date.Now.Ticks OnCreate Order: if Entity.Total > Entity.Account.MaxOrderTotal:
BeginManualApprovalFor Entity
From the perspective of the DSL, it is very easy to use, and from the implementer perspective, this was ridiculously easy to implement.
The only half-way complex thing here is the introduce base class action, which is usually the first approach that you take, which is why I don't consider it complex. Then you can just do create the base class:
abstract class DslBase: [property(Entity)]
entity as objectdef OnCreate(entity as System.Type, action as callable()): Actions.RegisterOnCreate(entity.Name, action) abstract def Execute(): pass def BeginManualApprovalFor(order as Order): print "Starting approval process for Order #${order.Id}"
In the base class you just do what you would normally do, in this case, it just register the new action. Here is the implementation:
class Actions: static onCreateActions = {} static def RegisterOnCreate(entityName as string, action as callable()): onCreateActions[entityName] = action static def OnCreate(entity as object): entityName = entity.GetType().Name action = onCreateActions[entityName] as callable() if not action: print "No action defined for ${entityName}" return dsl = (action.Target as DslBase) dsl.Entity = entity action()
The client code is simply:
SampleDSL().Prepare("Sample.boo")
account = Account(MaxOrderTotal: 100)
print "Before: ${account.AccountNumber}"
Actions.OnCreate( account )
print "After: ${account.AccountNumber}"
order = Order(Account: account, Total: 500 )
Actions.OnCreate( order )
All you have left then is to get a rich enough library of methods on the DslBase, for the business user to work with, document how this is done, and that is it.
Well, it has not gotten to that level, but I just hit ALT+Insert in #Develop, and I got this dialog:
Wow!
Yes, it sucks, and I feel really bad about it, but I will not be able to be at the ALT.Net conference. The main reason is that I was stupid and didn't take into account how long it will take to get a visa, and I also forgot that right now there is a huge concentration of holidays, so the embassy is apparently swamped with requests, and taking longer to process them even than the usual.
:-(
