Ok, that is a lot of details to consume. But in general, I think I understand the overall approach, its benefits and agree that it looks "kickass". The problems I see is "learning curve", which ties into the ability to decompose a problem into different "concern scopes". When the solution is provided like this, it may look simple, but when the domain is more muddy (normally the case) this can quickly become a huge obstacle in new projects (perhaps an excellent consulting business in itself!).
Having the Rest handler (seems almost enough reason to drop Restlet dependency) in a standard library with a full sample (let's say the User Profile case you talk about) in source code available is a good start, but we will need to write a lengthy piece on how this approach/pattern is done in a 'general' fashion, how to identify the contexts, how to separate the domain logic from other concerns, how to partition Rest URLs and many other details that quickly swamp someone how tries this on his/her own. It is also an interesting insight about the "can this method be called", and it would be really cool if Java allowed one to reference a method directly and statically, so a more direct approach could be taken. Guess we need to save that for "Qi, the language". Cheers On Mon, Feb 15, 2010 at 1:37 PM, Rickard Öberg <[email protected]> wrote: > Hi all, > > Now that Qi4j 1.0 is out the door, I think it's a good time to start > thinking about what's next. This is a long email, but please bare with me, > because there's some really crucial points in it I think. > > As you may have noticed the dev list is a bit quiet, but it doesn't mean > that nothing is happening. On my end, I'm working on the StreamFlow project > that uses Qi4j, and am coming up with a lot of things that eventually will > be transferred to the Qi4j project as libraries and extensions. > > I'd like to outline some of the highlights which I've found to be useful, > and which I think would be very useful for Qi4j projects in general. > > The first thing is method constraints, which we have already discussed. > Basically being able to put Constraint annotations on methods instead of on > parameters, with the semantics being "is this method valid to invoke at all > or not". This is something which I haven't seen any other framework do, and > I think it is crucial. One of the key thing it makes possible is the ability > for outside code to check *before* the method is called whether it is > possible or not. > > The basic idea would be something like this. In a mixin interface where a > method depends on the state of the entity, you could add: > @RequiresStatus(Status.ACTIVE) > void doSomething(...); > > Where RequiresStatus would have a Constraint implementation like: > public class RequiresStatusConstraint > implements Constraint<RequiresStatus, Status> > { > public boolean isValid(RequiresStatus annotation, Status status> > { > return status.currentStatus().equals(annotation.value()); > } > } > -- > The "status" parameter comes from simply casting the entity to the Status > interface, which may not necessarily be the one where the doSomething() > method is in. > > With this we can both check the constraint on actual calls *and* in client > code that wants to find out whether the method is valid at all, and use this > for changing the UI. > > That being said, what I've found in StreamFlow is that you don't really care > if the domain methods are valid. What is important is whether the *usecase* > that uses the domain method is valid. It would be the same with the Shiro > annotations that Paul is working on, since they don't really have anything > to do with the internal application state, but rather with if the usecase > that drives a domain model change can be invoked considering the context of > the usecase, which includes the user calling it, and the permissions of the > user. > > So how do you implement this? Here's where the DCI (Data, Context, > Interaction) paradigm comes to the rescue! For me it started by looking at > "how can I implement a REST API using Qi4j?". And then it struck me that URL > path's map naturally to the "context" in DCI! > > Let me show you with an example. Let's say we have the following REST URL: > /users/1234/profile > > The root ("/") does not map to an entity, and neither does "/users". But we > still want to model it in our application. How to do that? By using > TransientComposites! I can make a RootContext composite that looks like this > (this is actual code from StreamFlow): > @Mixins(RootContext.Mixin.class) > public interface RootContext > extends Context > { > @SubContext > UsersContext users(); > > ... other subcontexts ... > > abstract class Mixin > extends ContextMixin > implements RootContext > { > public UsersContext users() > { > return subContext( UsersContext.class ); > } > } > } > --- > "Context" above extends TransientComposite. ContextMixin has the > subContext() method which instantiates the given Context subtype. The key is > that I also inject an InteractionContext object into ContextMixin, using > @Uses, from the outside which has the following methods: > public class InteractionContext > { > private Map<Class, Object> roles = new HashMap<Class, Object>( ); > private List<Object> objects = new ArrayList<Object>( ); > > public void playRoles(Object object, Class... roleClasses) > { > ... put the object in the roles list and add mappings from > roleClasses->object in the map > } > > public <T> T role(Class<T> roleClass) > { > ... return object from mapping using roleClass or find object > in list that implements roleClass > } > } > So before handling a REST call I will populate the InteractionContext with > the User of the call, for example, so I can then do > "ContextMixin.context.role(AuthenticatedUser.class)" to retrieve it in code. > > If I have the above, then I can have a generic "DCI-REST" handler for "/" > which reflectively find that I have a @SubContext "users", and then present > it using HTML if I do a GET: > <h1>Subcontexts> > <ul> > <li><a href="users/" rel="users">users</a></li> > </ul> > --- > If the user puts in "/users" in the browser the handle will instantiate the > root, and then call users() to get the next level of the context. If I only > want logged in users to be able to do this I should be able to use the > @RequiresUser annotation that Paul mentioned *on the users() method*. This > ensures that any usecases in UsersContext can only be accessed by logged in > users, so there's no need to check for that in the levels below /users. > > What next? In UsersContext I will then put usecases as methods > (=interactions) that can be used to list users, create users, and select > them (actual code again): > @Mixins(UsersContext.Mixin.class) > public interface UsersContext > extends SubContexts<UserContext>, Context > { > UserEntityListDTO users(); > > EntityReference findbyusername( StringDTO name) throws ResourceException; > > // Commands > void createuser( NewUserCommand command); > > abstract class Mixin > extends ContextMixin > implements UsersContext > { > public UserEntityListDTO users() > { > ... list users and return as DTO. REST handler will convert to HTML > automatically > } > > public EntityReference findbyusername( StringDTO name ) throws > ResourceException > { > ... query to find user by name. Since method returns EntityReference > the REST handler can do redirect to "/users/<id>". > } > > public void createuser( NewUserCommand command ) > { > ... > } > > public UserContext context( String id ) > { > UserEntity user = uowf.currentUnitOfWork().get( UserEntity.class, id > ); > context.playRoles( user); > > return subContext( UserContext.class); > } > } > } > --- > In this case, instead of using @SubContexts to access static subcontexts, I > want the next level to be id-based because now I want the REST URL to > correspond to a particular user. The context(id) method is in SubContexts, > and using generics to make it statically typed. I also look up the actual > UserEntity, and put it in the context. The subContext call will instantiate > the UserContext and forward the InteractionContext so that any call from > here on that does "context.role(UserEntity.class)" or any other mixin type > in UserEntity, will return the user. So no code below here will have to know > about how to translate id's to a looked up user. > > Example: > /users/1234 > translates to: > <root>.users().context(1234); > > This I can use either for REST purposes, or any other client code (like > tests) that want to execute interactions on my domain model. > > If I do GET on "/users/1234" the REST handler will hence perform > <root>.users().context(1234) using reflection, and then show a generated > HTML form with what methods are available in UserContext. > > And here's where method constraints come in! I can now put constraints on > the interaction methods in UserContext, rather than in the domain model, > which declares whether an interaction is available or not. > > Example: > @Mixins(UserContext.Mixin.class) > public interface UserContext > extends Context > { > @RequiresUserIsOwner > void changePassword( ChangePasswordCommand newPassword ) throws > WrongPasswordException; > ... > } > When I generate HTML for the above, if the user making the REST call is not > the same as the user that is being shown, this method will not be shown in > the HTML. > > The constraint could be implemented as: > public class RequiresUserIsOwnerConstraint > implements Constraint<RequiresUserIsOwner, Context> > { > public boolean isValid(RequiresUserIsOwner a, Context c) > { > // Get the InteractionContext for this Context > InteractionContext context = c.context(); > // Look up roles from the context > // Authenticated user comes from REST handler > AuthenticatedUser auth = context.role(AuthenticatedUser.class); > > // User comes from subContext(UserContext.class) mapping > User user = context.role(User.class); > > return auth.user().equals(user); > } > } > --- > So this method constraint on the interaction only uses information *from the > call* rather than the domain level. > > If I want to be able to disable users in my system, in which case they are > not allowed to change passwords, I would instead do: > @Mixins(UserContext.Mixin.class) > public interface UserContext > extends Context > { > @RequiresUserIsOwner @EnabledUser > void changePassword( ChangePasswordCommand newPassword ) throws > WrongPasswordException; > ... > } > where EnabledUser is implemented as: > public class EnabledUserConstraint > implements Constraint<RequiresUserIsOwner, Context> > { > public boolean isValid(RequiresUserIsOwner a, Context c) > { > // Get the InteractionContext for this Context > InteractionContext context = c.context(); > // Look up roles from the context > // UserEnabled comes from subContext(UserContext.class) mapping > UserEnabled user = context.role(UserEnabled.class); > > return !user.isDisabled(); > } > } > --- > So with the above, I can both have constraints related to the request being > made *and* application state. If I only have constraints on domain methods > this would be impossible, since it doesn't have the extra context. Another > option is to put the @EnabledUser on the context() method that returns the > UserContext, so that effectively a disabled user "disappears" from the REST > API. > > What I've found is that with this I can easily model all the interactions in > my application, and I can then have a generic REST API handler that is > generating HTML on GET (including forms for interactions!), perform methods > on POST/PUT/DELETE, and also support HTML for humans browsing the API in a > webbrowser, and JSON for applications calling the same API. I can perform > everything in my application using the generated HTML. If I want a nicer UI > I create a rich client or an GWT client or similar. > > My REST API effectively also becomes self-describing, with no need for WSDL > or somesuch description language. If the generated HTML also has links to > the JavaDoc of the context composites a developer can "explore" the API > without having to have access to contexts and methods in a webbrowser. I'm > pretty sure there's no other framework on the market right now that can do > this, especially with the extra conditions that methods need to be > allowed/disallowed depending on call and application state. > > The same context/interaction composites above can then also be used by other > types of clients, such as BDD tests. > > So far I haven't even mentioned composable contexts, which is an added > bonus. Here's a quick example. I have many entities which should support > "descriptions", so they should all have interactions for > "changeDescription". Instead of duplicating that code in all my transient > composites I can do: > public interface SomeContext > extends DescribableContext, Context > { > ... > where: > @Mixins(DescribableContext.Mixin.class) > public interface DescribableContext > { > public void changedescription( StringDTO stringValue ); > > abstract class Mixin > extends ContextMixin > implements DescribableContext > { > public void changedescription( StringDTO stringValue ) > { > Describable describable = context.role( Describable.class ); > describable.changeDescription( stringValue.string().get() ); > } > } > } > --- > This partial context does not have to know *what* type of entity is having > its description change, only that it implements Describable and is available > from the context of the interaction. > > In addition, this solves the problem of how to do aggregate entities. With > aggregates the rule is that all interactions go through the "root aggregate" > so that any rules it has are enforced. Normally this would require that we > put all methods in the actual aggregate entity. This approach solves this, > since we can spread out the interaction methods in contexts, and still be > able to enforce aggregate rules. Example: let's say that when a user profile > is updated the last-modified-timestamp on the user is updated. The REST URL > would be: > /users/1234/profile?command=updateprofile > > In ProfileContext, which we would get from a > UserContext.subContext(ProfileContext) call, we do: > public void updateprofile(NewProfile profile) > { > UserProfile = context.role(UserProfile.class); > userProfile.updateProfile(profile); > User user = context.role(User.class); > user.updateLastModified(new Date()); > } > -- > Instead of putting this rule in the updateProfile() method, which would tie > it to the User class, or even worse, doing something like > user.updateProfile() which delegates to user.profile.updateProfile(), this > rule is encapsulated in a place where all the necessary context is available > (and if this rule should happen for all methods, put it in a generic Concern > on ProfileContext!). This way aggregate rules are much easiler to handle and > don't mess up the core domain model. > > Also, the REST handler can contain the logic that if we do > <root>.users().context(1234) it can then do a redirect to a server which > should handle user "1234" (if we have sharding in place). > > To sum up: by using transientcomposites along with a clever usage of the > InteractionContext we can support DCI fully, including subcontexts and > composable contexts (the DescribableContext "trick" above). This also > *happens* to be perfect for REST API's, and also for use in tests (i.e. you > do the tests on the context/interaction rather than the raw domain model). > > To me, this would be an important next step for Qi4j, and would make it > support DCI, REST, aggregates and BDD in a simple and logical way. > > WDYT? > > /Rickard > > _______________________________________________ > qi4j-dev mailing list > [email protected] > http://lists.ops4j.org/mailman/listinfo/qi4j-dev > -- Niclas Hedhman, Software Developer http://www.qi4j.org - New Energy for Java I live here; http://tinyurl.com/2qq9er I work here; http://tinyurl.com/2ymelc I relax here; http://tinyurl.com/2cgsug _______________________________________________ qi4j-dev mailing list [email protected] http://lists.ops4j.org/mailman/listinfo/qi4j-dev

