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