Monday, February 25, 2013

The Breeze Server: "Have it your way"

BreezeJS is a 100% JavaScript client library. It can work with any backend that speaks HTTP.

It's hard to get that message across when most Breeze samples demonstrate a Breeze client talking to a Web API controller with Entity Framework and SQL server behind it. Understandably, some people believe that Breeze only works with Web API, EF, and a SQL database. We need more samples to show alternatives. We're working on those.

In the absence of those samples, please sing along with me now, the unforgettable "Have it your way" jingle from the 1976 Burger King commercial. Curse me later for your inability to get that song out of your head.

For the hard of hearing, I'm reprinting an email I sent recently to my web developer friends on this subject.

Technorati Tags: ,,


I'd like to clear up some misconceptions about BreezeJS regarding the relationship between data on the client and data on the server. Want the gist? Head straight to the Summary at the end.

BreezeJS is a library for building CRUD applications in client-side JavaScript and HTML. A Breeze application depends on a JavaScript entity model defined for the application domain as that domain is understood on the client.

How closely the client entity model corresponds to a model on your server is entirely up to you. The client-side BreezeJS is happy to accommodate you!

For many developers, the independence of the client and server models is a matter of principle and they will go to great lengths (and write a ton of server code) to demonstrate and defend that principle.

Personally, I'd rather write less code. The hardest part of my application will be building a tolerable UI that meets the application requirements for functionality, data integrity, security, and usability. That's a tall order. I'm not eager to do more than I have to. Moreover, I would gladly postpone some heavy coding decisions today if I am confident that I can change direction tomorrow without a massive rewrite. That perspective informs my preferred Breeze development path. Your perspective may be different.

As I said, the Breeze client entity model need not correspond to any server model. But it's a heck of a lot easier when it does. We paved a rose-petal path for the .NET developer who is happy with a Web API service talking to Entity Framework for data access and mapping to a POCO entity model. Breeze.NET components make it easy to expose EF metadata and IQueryables to a Breeze client and, yes, they make it easy to open the kimono to the client. Fortunately, with just a little effort you can show a lot more discretion and remain productive.

I am unashamed to take this path myself. I have no desire to waste valuable customer time and money moving data into and out of DTOs "on principle". I am reluctant to “pollute” the server with hundreds of controllers each with 4 or more methods.

I am not kidding about the consequences. The typical business application has a minimum of 200 domain model types. 90+% of the time the shape of the data I'm sending over the wire is the same as the shape of the entity in my business model. An Order in the database often looks like an Order in the domain model and the Order in the domain model probably look like an Order on the client most of the time. When such an entity meets the JSON.NET serializer it might as well be a DTO.

When the entity type is simple, it is likely to have the same shape in every layer. In my experience, models are dominated by relatively simple types and types with no serious privacy concerns. There are usually a few hairy ones that need close attention; the vast majority to not merit that much love.

I know how to guard queries so that the right people get the right data. Of course I always inspect the change-set data coming from the client to make sure they actually should be saved before I tell my persistence machinery to save them. This essential effort is no more complicated or onerous with Breeze than any alternative you can propose.

You are welcome to disagree. I know that some of you do disagree. Fine. Have it your way. Replace the database with your favorite backing store. Create DTOs. Add more controllers with more fine-grained query and save methods to do exactly what you want. Don't like $expand or $select? Deny them! Can't figure out how to make IQueryable work for your DTOS? Don't use IQueryable. BreezeJS is happy to accommodate you!

Of course you'll be writing more code on the server. You've signed up for all the mapping, the DTO files, the controllers, layers, duplicate guard logic, and the whole nine yards. In no time you'll be praising an auto-mapper that automate away the tedium that, from my perspective, you never needed to endure in the first place. You'll paper your walls with intricate architecture diagrams and reams of API documentation. Good luck training your successors. Knock yourself out. BreezeJS is happy to accommodate you!

Realize that your server-side choices may oblige you to write more code on the client too. Most developers really like the Breeze LINQ-like style for writing ad hoc queries (a staple of LOB apps). Breeze translates these queries into OData URL query syntax before sending them to the service.

An OData service knows what to do with that syntax. A Breeze Web API ActionFilter can apply that syntax to your controller's IQueryable methods … if they exist and to the extent that they support the range of options supported by OData query syntax.

Don't like that syntax for your service? If your service API doesn't want to play, then your Breeze client shouldn't make those requests. The Breeze "EntityManager" can make a bare resource request to any HTTP endpoint. You can easily take on some simple data parameters if your service knows how to handle them. If you're more ambitious, you can replace the Breeze AJAX plugin ($.ajax by default) or write your own dataservice adapter. BreezeJS is happy to accommodate you.

When the server responds, all that matters to BreezeJS is that the response payload contains JSON data. When Breeze sees objects that it recognizes as entity types, it turns those object into entities and caches them. But the data can also be plain old JavaScript objects.

Yes, I frequently send non-entity data to my Breeze app too. I make liberal use of projections when I don't need entities. Sometimes I write the projections on the client; sometimes I write them on the server. When the shape of a client entity doesn't align well with the shape of a server-side business entity, I may switch to a DTO for that particular case. I won't do that if I don't have to. I know that I can do it whenever I want to.

You'll get more Breeze benefits if the objects in the query result payload actually are entities. Breeze enriches entity objects with data binding support (Knockout, Angular, Backbone), validation, change tracking, and whatever custom properties and behaviors you've defined for objects of that type. Breeze caches entities and you can ask it to serialize part or all of a cache to local browser storage for offline and intermittent connectivity scenarios.

How does Breeze know if the data objects are entities? It looks up their accompanying type names in its MetadataStore. What's in the store? EntityType definitions that describe data properties, navigation properties, validations, and custom behaviors. “Custom behaviors” are JavaScript methods you add to the metadata that describe the client-side EntityType. These methods may duplicate logic you have in a server-side entity model. More often they are application logic to improve the user’s experience of the entity.

Where did that metadata come from? Well, the easy way to get metadata on the client is to ask the server for it. Breeze accepts OData metadata. Breeze also accepts an extended form of OData metadata that you can generate with Entity Framework … another benefit of allowing the client entity model to be shaped like your EF domain model.

But you don't have to get your metadata from the server and the metadata don't have to correspond to any particular server-side entity model. You can create the metadata entirely on the client if you wish (as we show in our "NoDb" sample). BreezeJS is happy to accommodate you.

If you want Breeze to materialize your JSON data as entities, the metadata do have to match the "service model" exposed through your service. This service model does not have to be an entity model. It is simply the collection of "types" that you serialize to the client in response to API calls. That service model could consist entirely of DTO types … if that's how you want to roll. It's just more code, right?

I suppose you could send any damned thing over the wire if you were willing to catch it and parse it yourself. You can create entities on the Breeze client out of data from any source … and make them appear to have been queried entities. That's how I construct test entities for my automated tests. It's just more code. BreezeJS is happy to accommodate you.

Finally, there is the matter of saving client-side changes. The easy way is to ask Breeze to detect all pending changes (adds, mods, deletes) to all kinds of entities (e.g., orders and line-items) … bundle them up and send them to the server as a change-set. Breeze.NET helpers on the server give you ready access to the bundle so you can inspect it, reject it, modify it, map it … as your heart desires. If using the vanilla Breeze.NET EF helper, the bundle will be saved as a single transaction.

Alternatively, you can cherry-pick the entities to save in a bundle.

Got your own save commands, Mr. CQRS? It’s easy to pull sub-graphs from the client cache and shape your own command payload.

Btw, you can have as many separate caches (separate “EntityManagers”) as you like, each of the isolated from the others. It’s easy to flow entities across caches (e.g., reference lists for dropdowns) so you don’t have to go back to the server all the time. I often use separate caches for different tasks, e.g., “sandbox editors”). BreezeJS is happy to accommodate you.

Summary

My life as a developer is easier when I have the same EntityType shapes, end to end, and use Web API + Entity Framework to make it so. It can be almost as easy if the service is an OData service

I'm not worried about so-called tight coupling of the client to the server. I'm building CRUD apps. 9 out of 10 times the reason I'm changing the server-side entity model is in response to a business requirement affecting the client. I'm going to be making changes on both sides anyway in lock-step fashion. And … as I said … I can break the symmetry any time at any point in the model without tearing down the whole house.

But If you want DTOs everywhere, all the time, have at it. Breeze can still help you on the client with data binding support, validation, change-tracking, and caching.

BreezeJS is happy to accommodate you.

6 comments:

Anonymous said...

It would a great if there is an example solution with the popular Micro ORMs (like dapper or ServiceStack.OrmLite).

What would be even better is if some of these MicroORMs supported IQueryable.
Hoping someone can come up with a friction free solution in this area.
rockmeister

Anonymous said...

"I'm not worried about so-called tight coupling of the client to the server." - Best sentence I've read in the last years. Really.

Ward Bell said...

@A1 - we welcome efforts by the community to explore the MicroORM avenues and would be pleased to help where folks get stuck.

@A2 - Thanks

Anonymous said...

"I'm not worried about so-called tight coupling of the client to the server." - Best sentence I've read in the last years. Really.

Fully agree, the only advantage I got throughout my career as a successful developer is to clearly understand user requirements, and implement them quickly using a tight coupling CRUD software.

Any changes in the UI requires changes in all layers, and users want them quickly.

Special business rule and validation will be developed separately, and that how all the Apps, which I developed for at least 200 users, have minimum bugs (I am saying two or three bugs per release), and easy to maintain.

IanIan said...

How do you stop someone manipulating the call back and updating a User entity with, say, the admin role?

Ward Bell said...

Not sure which callback you're describing. On the server you either have eliminated the IsAdmin property from the public model or you simply reject any change to that property by an unauthorized user. If the property is mentioned in the OriginalValues map, throw an exception.