Previous Entry Share Next Entry
Things from Apps -- how they do and will work
device
jducoeur wrote in querki_project
So I added a small-but-damned-useful feature today: by default, all Things now display (under their title) a small link to their Model. This is one of those little details I've found myself wanting, when, eg, I am looking at a Thing and say, "Oh, right -- I need to add another Property to this Model".

That was pretty easy, but of course, as usual (let's all echo the mantra) Querki Plays Fair. Which means that this link *always* shows, even if the Model isn't defined in this Space. This is of course utterly common -- most Things are currently derived from Models in the System Space (everything ultimately inherits from System::Thing), and in the long run it'll be common for you derive from Apps. Suddenly, it is really easy to navigate into System, and see what the Things in there look like.

The problem, of course, is that I quickly realized that this lets you *edit* Things in System, and that's a no-no -- the System Space is hardcoded. Unfortunately, everything works very nicely to produce a *copy* of that System object in your local Space -- your wanderings are contextualized by the Space that you are in -- and that's kind of a recipe for havoc.

So for now, I've simply disallowed that -- if you try to edit a System Thing, you get a warning in the Editor window, and it disables the Done button. That's a bit crude and insecure, but should do for the time being.

It does raise the question, though: what *should* happen if I try to edit a System Thing? This has actually been in the architecture for a long time, but I don't think I've ever talked about it before.

Desired Functionality

The thing is, it's entirely reasonable to want to edit something from System, or more generally from the App(s) my Space derives from. After all, Querki is all about tweakability, and it would be annoying if I couldn't tweak the things I'm inheriting from.

So what I would *like* to see, if I try to edit, say, Simple Page from my Space, is that it should seem to Just Work -- in that Space. Of course, my changes mustn't affect anybody *else's* Spaces. But I ought to be able to think that I have complete control over the Things in my Space.

Implementation

So how will that work? Here's the theory.

In the long run, Things in my Space won't necessarily inherit directly from their Models in Apps. In fact, they might *never* do so, to make things more consistent. Instead, when I instantiate a Thing from a "foreign" Model, Querki will automatically create a "shadow" of that Model, locally, and that's what the local Things will inherit from. The shadow is itself simply an empty sub-Model inheriting from the foreign Model, with the same name. Initially, it defines no properties or values of its own -- it just inherits them from its foreign parent -- but it *can* be edited, pretty much at will.

This approach will cost a bit of Thing overhead, but I suspect not much -- most Spaces are likely to have orders of magnitude more instances than models. And it allows deep customizability of my Space, to work a bit differently from the defaults, living up to Querki's Maker ethic.

Querki and Pull Requests

There's another reason for this approach, though: the fact that, deep down, Querki kind of wants to be Git, and is stealing many of Git's ideas, including the Pull Request.

For those who don't know it, Git is the currently-trendy tool for managing source code. It's quite nice in many ways, and has quickly become almost universal among the open-source community. The main reason for that is its attitude towards changes and repository. There's no real concept of a "master" repository in Git -- instead, there are just lots of little copies of the code scattered around. If you've made some changes that you think are good, and you want another repository (such as GitHub, which serves as the central place for much open source) to pick it up, you submit a "pull request" -- basically, you say "I think you should pull my changes into your repository".

In the long run, this is likely to be the right attitude for Querki. Querki's Apps aren't envisioned as hard-and-fast products like most -- ideally, each App is essentially a community of Spaces that are deriving from it. And I would like to empower that community to contribute to the development and maintenance of the App, by making it easy for derived Spaces to contribute their changes.

When I want to submit a "pull request" for an App, I say which Models, Things and Properties I believe should be pushed up. If I own the App, I can do this by fiat (indeed, Querki's planned App-development cycle is to make and test changes in my local Space, then push them up to the shared App); if not, I submit a request that the community can discuss, and the App's owner can decide whether to accept the changes.

Which is where the shadow Models come in. By default, pull requests will consist of all of the changes I have made to the inherited Models and Properties. If my request is accepted, my changes are pulled up to the higher level, and removed from my local copy -- the local copy typically gets reset to being a thin shadow again, inheriting the new values from the App.

Making Communities Hum

This is very experimental stuff, and we'll see how it works in practice. But the high concept is to gradually adopt the best practices from the open source world into the consumer world of Querki, encouraging folks to work *together* to gradually build Apps that really work well for their communities.

Opinions? This is one of those places where Querki is being pretty radical, trying to break down the priesthood of programming and democratize the good ideas there. I am quite sure that most users will never participate to this level. But if even 5% do, that's potentially a *lot* of people involved who have never touched a line of code...
Tags:

  • 1
The thing is, it's entirely reasonable to want to edit something from System, or more generally from the App(s) my Space derives from. After all, Querki is all about tweakability, and it would be annoying if I couldn't tweak the things I'm inheriting from.

Oh, gods, yes. I went through a summer of C++ hell because Borland had declared their String class to be final (or perhaps it was just one method on the class) for no good reason that I could tell, necessitating absurd workarounds for something that should have been a 5-minute "inherit and modify" change. (It wasn't even like I was extending functionality, I was fixing a bug. But I didn't have time to wait for the official fix release.)

By default, pull requests will consist of all of the changes I have made to the inherited Models and Properties. If my request is accepted, my changes are pulled up to the higher level, and removed from my local copy -- the local copy typically gets reset to being a thin shadow again, inheriting the new values from the App.

I like the general approach, esp. since "by default" implies that one could nominate some subset of Models/Properties instead.

How are rollbacks handled, in case of buggy code, things that don't quite work right, user uproar, or (worst-case) malicious code being pushed up the stream? I know you're planning a robust versioning system - does it handle cross-space changesets? Can remove a set of changes even if there have been other local changes made afterwards?

How are rollbacks handled, in case of buggy code, things that don't quite work right, user uproar, or (worst-case) malicious code being pushed up the stream? I know you're planning a robust versioning system - does it handle cross-space changesets?

That'll be an interesting problem. Honestly, I'm not sure -- this is one of those cases where I suspect we'll find it challenging, and doing it well may require some negotiation between the pusher and pushee in order to not break anybody's world. (That is, the rollback itself is easy, but we'll need to think about the correct handling of the Space that the changes were pulled from.)

The uproar problem is somewhat ameliorated by one important detail: my Space doesn't so much derive from the App per se, as from a specific *version* of the App. The Space should carry which App version it is based upon, so that it can decide when and if it picks up changes. This allows folks to gradually adjust to changes if they want to. In principle, we should also support forks, so that the community can fork off a variant from the older version if they don't like a change.

Can remove a set of changes even if there have been other local changes made afterwards?

I was just thinking about this yesterday. It's tricky, but I suspect it will be necessary -- there are too many cases where we will want to roll back specific changes, not the entire Space.

Indeed, it was occurring to me that this is the normal case for "Undo" -- I will most often want to just undo my changes to this specific Thing, rather than rolling back the world state.

The plan is that the History table is basically going to be a sequence of Undos. So there are essentially two primary modes for using it. If I want to undo a specific change, it will pull that record off the Undo stack and apply it. If I want to roll back the whole world to a previous state (or, more often, I want to *view* the world as it was in a previous state, which I expect to be a more common operation), then it takes the whole stack and rolls back through it.

In this specific case, things get complex, because the post-pull modifications could depend on the contents of the pull. We may have to get to that actually happening once or twice and figure out how to respond...

Never use C#.

Ignoring that you need the CLR or Mono or equivalent, most of the language is actually a pretty clean and decent OO variant of C.

On the other hand, WAY to many classes are declared final for no good reason. For a lot of data classes in the .NET library, like strings, this is particularly annoying as (1) their data are immutable, (2) the objects dynamically calculate their GetHashCode() method every time it is needed (and several times it isn't), and (3) GetHashCode() is not particularly efficient.

IIRC, making a hash indexed by strings requires the hash code to be calculated 3-4 times per mutator method and at least twice per accessor method.

(Which, if you are perhaps writing software that deals with real-time stock pricing, may be thousands, if not tens or hundreds of thousands, of times per second.)

I think that's a good argument that C# is a bad fit for that use case. In general, it's inadvisable to trust Microsoft for anything that needs blistering speed. That was driven home to me at a MS seminar about five years ago, talking about the newest tools and technologies they were introducing, when one of the engineering managers said, in more or less as many words, "Yes, this is about a thousand times slower than the way it used to run. We don't care -- it's scalable, and nowadays that's the only thing that really matters." (Paraphrased, but the "thousand times slower" stuck with me.)

It's an entirely reasonable attitude, IMO -- scalability *is* a key priority these days, as we stop building faster cores and begin to instead focus on ever-*more* of them. Most companies are content to throw hardware at problems, and that's usually about scalability. But if you're doing numeric processing, and speed is the uber-priority, it's totally wrong.

Keep in mind, before starting the Querki project and moving into full-time Scala, I spent about ten years mostly on C#, and I generally respect the language. I don't agree with all of their decisions, but they've done a *far* better job of keeping up with the times than Java has. It says a lot that I am still willing to work in C#, but haven't been willing to code in Java for 5+ years now.

(Of course, speed *was* uber-important at Memento, which led us to a slightly Frankensteinian architecture, using C# for most of the system but, eg, breaking out to external programs for sorting.)

*If* you are working in a Windows (that is, corporate) environment, and you want a solid general-purpose utility language (that works on the large scale, and does everything decently if nothing optimally), C# is still the best choice. (Better if you mix it liberally with F#.) If those are in any way *not* the case, there are probably better options...

  • 1
?

Log in

No account? Create an account