Hi Vincent,
It's good to have you back :)
On 11/13/2012 08:15 AM, Vincent Massol wrote:
Hi Caleb,
Thanks for taking the time to provide this feedback!
See below.
On Sep 27, 2012, at 9:52 AM, Caleb James DeLisle <calebdelisle(a)lavabit.com> wrote:
Hi,
I took a look at the new model proposal and there are a few questions and suggested
changes.
https://github.com/xwiki/xwiki-platform/blob/feature-newmodel/xwiki-platfor…
Should this be a String rather than a Java UUID type?
Right now it's a String. I don't recall why I proposed String rather than UUID,
maybe to allow for changing the implementation in the future.
https://github.com/xwiki/xwiki-platform/blob/feature-newmodel/xwiki-platfor…
If we use getChildren() rather than getChildReferences(), we will be mangling the model
with the database code which violates the MVC principle.
I don't agree with this. MVC has nothing to do here. In MVC, the M stands for Model
and this is the only thing we're talking about here. V (for View) and C (for
Controller) are outside of the scope.
What you hint at (I think) is a Layered Architecture and this we're going to do, by
having the persistence layer in a different layer. However business objects are completely
ok to call the persistence layer.
My idea is to use a Rich data model
This is not maintainable in the long term.
(as the current model BTW),
Which is why we're here...
i.e. business operations are *in* the model object
interfaces.
Aka the controller is mixed with the model.
This is where we need to agree. I personally don't
want to use an Anemic model where you have business objects be just data holders (ie. only
getters/setters) and move all business operations into a service layer.
What if you wanted to deserialize a bunch of documents in an XAR, add an object to each
one of them and then reserialize them?
I tried to do this at one point and found out that the only two options were manipulating
the XML or bringing up an entire wiki because XWikiDocument depends on everything.
Another case is when storage changes so radically that the storage API needs to change.
Imagine for that JDBC becomes able to do asynchronous load and store with a callback.
In order to take advantage of this improvement we would need to find all of the places
where code synchronously calls saveDocument() and replace them with code that understands
callbacks.
This isn't too bad if all of the controller code is severable from the model but if
the model calls into the store and it offers store APIs to it's consumers (basically
everything depends on the model), something as simple as implementing a (radically) new
store will land us back here talking about model 3.0.
Even just making a change to the store implementation will break this model, how do I load
a document from one store and save it to another if the document is aware of it's
store?
Taken another way, the store has to depend on the model because it handles these objects
in order to persist and load them, to make the model include APIs which depend on the
store creates a cyclical dependency loop, the basic building block of spaghetti code.
I took the time to read this all over again very carefully and with a clear mind.
I have come to the same conclusion about Fowler's arguments as before but I have also
noticed that he does not support your idea of mixing Document and storage logic.
Quoted from your
martinfowler.com link:
"It's also worth emphasizing that putting behavior into the domain objects should
not contradict the solid approach of using layering to separate domain logic from such
things as persistence and presentation responsibilities. The logic that should be in a
domain object is domain logic - validations, calculations, business rules - whatever you
like to call it."
Now for completeness, although this is off the topic of the model, I will address the
flaws in Fowler's argument.
His most prominent point is "it's not really object oriented" which is an
obvious case of
http://en.wikipedia.org/wiki/Law_of_the_instrument
"Give a small boy a hammer, and he will find that everything he encounters needs
pounding." having once been a small boy with a hammer, the quote still makes me
giggle..
The point is that sometimes objects make huge sense, other times procedures, sometimes
functional programming works best, sometimes a combination of the 3 or something else
entirely. The only "one true way" is to use the right tool for the job.
Aside from the "only true way" argument, Fowler doesn't offer much to back
his claim that lean objects are bad.
"The primary cost is the awkwardness of mapping to a database, which typically
results in a whole layer of O/R mapping"
I suppose this was written before Hibernate/DN/Spring/etc existed making this trivial so
it wasn't wrong when he wrote it.
Now aside from the zealotry rubbish and the ORM mapping issue which nolonger exists, I
don't see any reasons that lean objects are bad and finally I reject the word
"anemic" as a weasel word in this context as it is clearly a distraction from
the issues.
Now back to the model:
The wiki article you linked to makes a few good points about the value of adding code to
your objects. Validation and functions like clone() make perfect sense inside of an
object.
What makes no sense is having references beyond the object's scope to things like
serializers, name resolvers and storage.
I think a good rule is that I should be able to create a model object using new Document()
and have it fully functional without the need for any other part of the wiki.
Also we run
the risk of encouraging users to walk the tree looking for the entity it wants rather than
creating a reference to it and using that.
Walking the tree would obviously incur enormous unnecessary database load.
That's the reason for the Iterator:
EntityIterator<Entity> getChildren(EntityType type)
With an Iterator the implementation can do things like:
* get a single item at a time from the DB
* get a bunch of N items at once (in one request) from the DB
Now you have a valid point that we may also want to have the ability to get children
references. It's a good question. Now if we imagine that parents are stored as RDBMS
column in a document table. Getting all children documents will mean doing a select in the
document table where parent = a given parent ref ("select parent from document doc
where doc.parent = ?"). Now getting getChildren() would do: select * from document
doc where doc.parent = ?. The extra cost would just be the loading of
marshalling/unmarshalling of data (which you would need thereafter in your logic). But I
agree and it's possible that on other stores, it could cost less to get references.
So I'd propose to have 2 methods: one for getting only children references and one
for getting all children entities.
Of course this can't be implemented without the document containing a reference to the
store, back to the above issue.
https://github.com/xwiki/xwiki-platform/blob/feature-newmodel/xwiki-platfor…
Do we want to continue with the concept of the global variable representing the
"current" document?
There's no global variable. It's a thread local variable (i.e. visible only for
the current thread).
Yes I'd like to continue this. We didn't have it before in oldcore and
XWikiContext was passed around everywhere which is why we moved to ThreadLocal.
It's a global variable which points to a thread local.
Even though only that thread can see it, the entire codebase can access it so from a
maintainability standpoint, it's a global.
I'm currently not brimming with alternative
ideas but the heavy reliance on the XWikiContext has lead to
a lot of inter-modular communication which makes oldcore difficult to maintain, perhaps
we can prevent
this in the new model.
oldcore doesn't use this since it uses XWikiContext and not the Execution Context.
Maybe you could explain in more details what you mean by "lot of inter-modular
communication"?
It's hard to define but I can give an example:
Module A puts a few things into the context and module B, C, and D take them out and use
them.
Now you want to remove A and you refactor everything which explicitly depends on it and
pull it out
and everything explodes because there are hidden dependencies via other modules expecting
things to be
placed in the context. As long as your context is a Map<String, Object> there will
always be this
possibility. Dependency injection cleverly avoids this issue by using a Map<Class,
Object> where you have
to have a reference to the class before you can have an object of that class so by
controlling access to
the class, you control how far the implementations leak out.
https://github.com/xwiki/xwiki-platform/blob/feature-newmodel/xwiki-platfor…
I would rather this have a name which doesn't collide with a java.lang class. When I
see Object in a .java
file I would like it to always mean java.lang.Object.
https://github.com/xwiki/xwiki-platform/blob/feature-newmodel/xwiki-platfor…
I like XClass better for this purpose, it is essentially a class and creating a new name
which nobody
has ever heard of will just steepen the learning curve and make things hard on newcomers.
This has been discussed and voted on another thread so these names will be modified to
match the result of that vote.
ok
https://github.com/xwiki/xwiki-platform/blob/feature-newmodel/xwiki-platfor…
Why not give the user direct access to the property without the intervening wrapper?
Yes that's possible. However there could other methods such as a method to get the
property definition.
https://github.com/xwiki/xwiki-platform/blob/feature-newmodel/xwiki-platfor…
I don't like this.
A Persistable is saved in a store by a user. Who is the user and where are they saving
it?
The user who started the session.
Another global var which will lead to more "saveAsAuthor()" and
"saveWithProgrammingRights()" etc. which swap around the global variable around
before and after doing the operation.
What if I want to have multiple stores?
Then set the store to use when you start the session.
This global var will lead to more try/finally traps where the storage is swapped around
for one operation.
Must there be a global variable representing the
store which is implied by this function?
The comment and minorEdit fields are also a bit dubious, perhaps in the future they will
make no sense, perhaps
they should really be attributes of the Document, also the "attributes"
parameter reminds me of this:
http://geek-and-poke.com/2012/06/how-to-create-a-stable-api.html
Yep, that was exactly the reason for the attributes parameter ;)
The fact that you're adding a ffu map to a function indicates that it should be a
function of the store, not of the model.
That way if the store is changed the parameters can change.
We can tune this API and remove comment/minorEdit if we want. I'm not sure about
putting them in Document; it's strange from an API POV IMO. I wouldn't want to use
such as API where you have to remember to set the comment in the Document before calling
save...
We can wrap the generic thingstore with a documentstore class/interface which takes
precisely the parameters we want and prepares the document for storage.
Finally, giving the document awareness of where
and how it is stored violates the MVC boundries.
Don't agree, see above. Nothing to do with MVC IMO.
I think we should not worry about the API which
stores the Persistable when we are creating the model, the
store API should be separate so that it can evolve while the model stays stable.
See above, anemic vs rich model.
And Fowler said mixing model with storage is not what he means..
A Session is form a user POV. Technically it will use a Transaction but that's a
technical implementation and it's more than that. It contains a user; it can contain
the store to use, etc.
Ok, that's valid.
Also why does it extend Persistable?
How do you persist a session?
Saving a Session means saving all entities that have been modified and have not been
saved so far.
This is the same as JCR (a lot of stuff come from JCR btw):
http://www.day.com/maven/javax.jcr/javadocs/jcr-2.0/javax/jcr/Session.html
Note that JCR 2.0 has deprecated Item.save() and I've researched this a bit but
couldn't find the reason beyond "they wanted to simplify the API" and
it's causing problems to users who want to just persist a given Item and what's
below and the not the whole Session. Which is why I've kept having Entities implement
Persistable so far.
Hmm if session is going to be aware of all things which have been altered by it then it
must provide them.
If I get a document from the Server then call save() on the session and that document is
saved, that is two apparently unrelated objects having an effect on each other which is
nonsensical.
I'm not sure how much use there will be for that but one approach is to pass the
session to the save functions and this would communicate the user to save as and the store
to save in, it would however tie your hands a little bit.
https://github.com/xwiki/xwiki-platform/blob/feature-newmodel/xwiki-platfor…
Why does addSpace() not take a Space parameter? it makes sense to me that you would do:
parent = storeApi.get(wikiRef, "SomeSpace", user);
child = new DefaultSpace();
child.addDocument("ADocument", new DefaultDocument("This is
content."));
parent.addSpace("ChildSpace", child);
storeApi.save(parent, user);
I prefer the current API:
Entity parent = …;
Space newSpace = parent.addSpace("child space");
Document newDocument = newSpace.addDocument("a document");
newDocument.setContent("This is content");
// Save the new entities created below parent
parent.save(…);
// Note: if we wanted to save all entities modified(not just the one in this tree) we
would write:
session.save(…);
I'm just brainstorming here but this seems
like a reasonable approach..
Your solution doesn't hide the implementation (DefaultSpace() and DefaultDocument()
in this case) and makes it a lot harder to unit test. We don't want to repeat the
issue we have today with unit testing XWiki/XWikiDocument...
What issue is this? Usually the issue I run in to is I have to bring up almost a whole
wiki just to instantiate an XWikiDocument.
Anyway I'm sure there are other ways which respect the API consumer's right to use
their own implementation of Space or Document.
I noticed the lack of a User data type, is that
just something which is planned for the future or is it
intentionally missing?
Good question. I don't know yet. A possibility is to pass the user in the save()
call. Another is to use the Entity's author.
+1 for passing the user to any call which needs permissions checking.
My last question is how do you differentiate this from the oldcore model?
In order to make the case that we need to have a new model, we need to prove that in 8
years this isn't going to be the same as the oldcore is now.
Thanks,
Caleb
Note that we also need a new User module anyway.
Thanks
-Vincent
_______________________________________________
devs mailing list
devs(a)xwiki.org
http://lists.xwiki.org/mailman/listinfo/devs