Multi tenancy with spring data jpa and eclipselink - eclipselink

I'm trying to add multi tenancy support to my Spring data jpa repositories. I would like to set dynamically the tenant id per request, but it does not work for the custom finder findBy* methods on repository.
I've followed this guide: http://codecrafters.blogspot.sk/2013/03/multi-tenant-cloud-applications-with.html
My repository looks like this:
public interface CountryRepository extends PagingAndSortingRepository<Country, Long> {
Country findByName(String name);
Country findByIsoCountryCode(String isoCountryCode);
}
I'm getting the error below when I call any of the custom finder findBy* methods on the repository interface:
javax.persistence.PersistenceException: Exception [EclipseLink-6174] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.QueryException
Exception Description: No value was provided for the session property [eclipselink.tenant-id]. This exception is possible when using additional criteria or tenant discriminator columns without specifying the associated contextual property. These properties must be set through Entity Manager, Entity Manager Factory or persistence unit properties. If using native EclipseLink, these properties should be set directly on the session.
Query: ReadAllQuery(referenceClass=Country sql="SELECT ID, TENANT_ID, CONTINENT, CREATED_BY, CREATED_DATETIME, CURRENCY, INDEPENDENTFROM, ISOCOUNTRYCODE, LONGNAME, MODIFIED_BY, MODIFIED_DATETIME, NAME, POPULATION, REC_VERSION FROM COUNTRY WHERE ((NAME = ?) AND (TENANT_ID = ?))")
at org.eclipse.persistence.internal.jpa.QueryImpl.getSingleResult(QueryImpl.java:547)
at org.eclipse.persistence.internal.jpa.EJBQueryImpl.getSingleResult(EJBQueryImpl.java:400)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:360)
at com.sun.proxy.$Proxy56.getSingleResult(Unknown Source)
at org.springframework.data.jpa.repository.query.JpaQueryExecution$SingleEntityExecution.doExecute(JpaQueryExecution.java:197)
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:74)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:97)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:88)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:421)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:381)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.invoke(CrudMethodMetadataPostProcessor.java:111)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy52.findByName(Unknown Source)
I assume that spring data generates the implementation of those custom finder findBy* methods at the initialization phase and put them into a cache with the current entity manager without a tenant id set on it and I am not able to set/change the tenant id on this cached entity manager. I'm trying to change the tenant id on the entity manager dynamically per request, so the question is how can I change/set the tenant id on that cached entity manager, which is used when I call any of the custom finder findBy* methods.
Here is my multitenant querydsl repository implementation:
public class MultiTenantQueryDslJpaRepository<T, ID extends Serializable> extends QueryDslJpaRepository<T, ID> {
private final CurrentTenantResolver currentTenantResolver;
protected final EntityManager entityManager;
public MultiTenantQueryDslJpaRepository(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager, CurrentTenantResolver currentTenantResolver) {
this(entityInformation, entityManager, SimpleEntityPathResolver.INSTANCE, currentTenantResolver);
}
public MultiTenantQueryDslJpaRepository(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager, EntityPathResolver resolver, CurrentTenantResolver currentTenantResolver) {
super(entityInformation, entityManager, resolver);
this.currentTenantResolver = currentTenantResolver;
this.entityManager = entityManager;
}
protected void setCurrentTenant() {
entityManager.setProperty(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, currentTenantResolver.getCurrentTenantId());
}
#Override
protected JPQLQuery createQuery(final Predicate... predicate) {
setCurrentTenant();
return super.createQuery(predicate);
}
#Override
public void delete(final T entity) {
setCurrentTenant();
super.delete(entity);
}
#Override
public T findOne(final ID id) {
setCurrentTenant();
return super.findOne(id);
}
#Override
public void deleteInBatch(final Iterable<T> entities) {
setCurrentTenant();
super.deleteInBatch(entities);
}
#Override
public void deleteAllInBatch() {
setCurrentTenant();
super.deleteAllInBatch();
}
#Override
public T getOne(final ID id) {
setCurrentTenant();
return super.getOne(id);
}
#Override
public boolean exists(final ID id) {
setCurrentTenant();
return super.exists(id);
}
#Override
protected TypedQuery<T> getQuery(final Specification<T> spec, final Sort sort) {
setCurrentTenant();
return super.getQuery(spec, sort);
}
#Override
public long count() {
setCurrentTenant();
return super.count();
}
#Override
protected TypedQuery<Long> getCountQuery(final Specification<T> spec) {
setCurrentTenant();
return super.getCountQuery(spec);
}
#Override
public <S extends T> S save(final S entity) {
setCurrentTenant();
return super.save(entity);
}
}

The solution is based on eclipse-link specific handling of BindCallCustomParameter that is added as tenant holder to EM property map.
public class TenantHolder extends BindCallCustomParameter {
private final TenantResolver tenantResolver;
private String defaultTenant;
public TenantHolder(String defaultTenant, TenantResolver tenantResolver) {
this.defaultTenant = defaultTenant;
this.tenantResolver = tenantResolver;
}
public String getDefaultTenant() {
return defaultTenant;
}
#Override
public void set(DatabasePlatform platform, PreparedStatement statement, int index, AbstractSession session) throws SQLException {
String resolvedTenant = resolveTenant();
platform.setParameterValueInDatabaseCall(resolvedTenant, statement, index, session);
}
private String resolveTenant() {
return tenantResolver.resolveTenant(defaultTenant);
}
}

Disclaimer: This does not answer the above query but provides an alternative.
Using bytecode instrumentation, I have created a java example on Multi-Tenancy (Table per Tenant) with Eclipse Link and Spring Data. This idea is chosen to utilize the complete power of Spring Data.
One can execute MultiTenantTest to see it working.
The idea is open-sourced and is available at Maven Central
Steps:
1.Include dependency
<dependency>
<groupId>org.bitbucket.swattu</groupId>
<artifactId>jpa-agent</artifactId>
<version>2.0.2</version>
</dependency>
2.Create a class as shown below. Package, Class and method has to be exactly same.
package org.swat.jpa.base;
import javax.persistence.EntityManager;
public class EntityManagerFactoryListener {
/**
* This method is called by JPA Agent.
*
* #param entityManager the entity manager
*/
public static void afterCreateEntityManager(EntityManager entityManager) {
//Business logic to set appropriate values in entityManager
}
}
3.Add javaagent when starting java
-javaagent:{path-to-jpa-agent-jar}

Related

JAX-RS security role-based (#RolesAllowed) entity filtering

In a JAX-RS application, some of my resources must be filtered depending on which roles the signed-in user has been assigned to. I'm trying to accomplish this using security annotations (#RolesAllowed, #DenyAll and #PermitAll).
This is what I'm looking for:
public class MyEntity {
public String getPublicString() {
...
}
#RolesAllowed("secretRole")
public String getSecretString() {
...
}
}
#Path("/myResource")
public MyResource {
#GET #Path("/{id}")
public MyEntity get(#PathParam("id") int id) {
...
}
}
Now, everyone (anonymous and logged-in users) can GET MyResource and retrieve MyEntity (per id), but for users in role secretRole, I'd like to see the output (here serialized as JSON):
{
"publicString": "...",
"secretString": "..."
}
And other users (either anonymous or otherwise users not acting on role secretRole) should see just:
{
"publicString": "..."
}
I know Jersey has entity filtering (and an implementation that filters based in security roles).
Unfortunately Liberty (Apache CXF based) has no such feature.
What have I done so far?
Since my solution deals primarily with JSON - using Jackson - I did some work based on Jackson's BeanSerializerModifier. Forget BeanSerializerModifier: it gets called only once per bean type (so the first user defines which properties get serialized for all other users - no, thanks).
Just found another Jackson concept that is applied each time a bean is about to be serialized: PropertyFilter and JsonFilter.
It kind of works, the implementation being very simple:
new SimpleBeanPropertyFilter() {
#Override
protected boolean include(BeanPropertyWriter writer) {
return include((PropertyWriter)writer);
}
#Override
protected boolean include(PropertyWriter writer) {
if (writer.findAnnotation(DenyAll.class) != null) {
return false;
}
RolesAllowed rolesAllowed = writer.findAnnotation(RolesAllowed.class);
if (rolesAllowed != null) {
boolean anyMatch = Arrays.stream(rolesAllowed.value())
.anyMatch(role -> securityContext.isUserInRole(role));
if (!anyMatch) {
return false;
}
}
return true;
}
}
And what's missing?
The Achilles' heel in above implementation is the securityContext reference (expecting an instance of SecurityContext).
I couldn't find means to get hold of a reference to the current security context.
Usually securityContext is #Context injected - either as a method parameter or as a field parameter. None of this is available to a BeanSerializerModifier.
I've managed to inject #Context SecurityContext (both by field or by constructor parameter); it happens to be a ThreadLocalSecurityContext in Liberty. BUT its method isUserInRole only works for the first request (when the ObjectMapper is created); then the reference gets stale and any other invocation throws NPE (inside isUserInRole method; the securityContext is still a valid java object reference; though referencing a stale object).
What are my constraints?
Jersey is not an option for me. I'm bound to Liberty (which is Apache CXF based).
I'm already used to Jackson, but it is not a must. JSON and REST are.
EDIT
HOLD ON: I thought the problem was the securityContext, but perhaps it is not the culprit. In time: I've managed to inject #Context SecurityContext (both by field or by constructor parameter); it happens to be a ThreadLocalSecurityContext, so I suppose it will get the actual principal from threadlocal storage.
BUT now I realized that BeanSerializerModifier#changeProperties gets called just once (for each bean), then the list of changed properties gets reused! I'll look closely at the Jackson specs; maybe I'll switch to JSON-B, as pointed by #Andy McCright (if its PropertyVisibilityStrategy doesn't also cache the result).
EDIT 2
Previous implementation with BeanSerializerModifier:
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
return beanProperties.stream()
.filter(property -> {
if (property.findAnnotation(DenyAll.class) != null) {
return false;
}
RolesAllowed rolesAllowed = property.findAnnotation(RolesAllowed.class);
if (rolesAllowed != null) {
boolean anyMatch = Arrays.stream(rolesAllowed.value())
.anyMatch(role -> securityContext.isUserInRole(role));
if (!anyMatch) {
return false;
}
}
return true;
})
.collect(toList());
}
I've managed to handle an instance of SecurityContext over a ThreadLocal. To this end I've implemented a ContainerRequestFilter:
static final ThreadLocal<SecurityContext> tlSecurityContext = new ThreadLocal<>();
#Provider
public static class SecurityContextSavingRequestFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
tlSecurityContext.set(requestContext.getSecurityContext());
}
}
Then tlSecurityContext.get() can be used as the current SecurityContext.
I don't know, however, if this is invalid or otherwise not recommended by JAX-RS spec.
Beyond this I've also switched to JSON-B (from Jackson) because:
it has better integration with Liberty (both server and client JAX-RS) by means of feature jsonb-1.0;
property filtering is less verbose (than Jackson's PropertyFilter), although less powerful too.
Full solution follows (with comments):
A ContextResolver<Jsonb> to configure Jsonb:
#Provider
public class JsonbConfigContextResolver implements ContextResolver<Jsonb> {
#Override
public Jsonb getContext(Class<?> type) {
return JsonbBuilder.newBuilder().withConfig(getConfig()).build();
}
private JsonbConfig getConfig() {
return new JsonbConfig().withPropertyVisibilityStrategy(new SecurityPropertyVisibilityStrategy());
}
}
A PropertyVisibilityStrategy to implement filtering proper:
public class SecurityPropertyVisibilityStrategy implements PropertyVisibilityStrategy {
#Override
public boolean isVisible(Field field) {
return false;
}
#Override
public boolean isVisible(Method method) {
if (method.getAnnotation(DenyAll.class) != null) {
return false;
}
RolesAllowed rolesAllowed = method.getAnnotation(RolesAllowed.class);
if (rolesAllowed != null) {
boolean anyMatch = Arrays.stream(rolesAllowed.value())
.anyMatch(role -> isUserInRole(role));
if (!anyMatch) {
return false;
}
}
return true;
}
And finally the ThreadLocal hack itself:
private boolean isUserInRole(String role) {
return securityContext.get().isUserInRole(role);
}
private static final ThreadLocal<SecurityContext> securityContext = new ThreadLocal<>();
#Provider
public static class SecurityContextSavingRequestFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
securityContext.set(requestContext.getSecurityContext());
}
}
}

camel custom marshalling with dataFormat name in header

I'm having two routes in two separated projects :
First route is setting the header with a data format bean name as a constant :
setHeader("dataFormatBeanName", constant("myFirstList"))
First route :
public class MyTest {
#Configuration
public static class MyTestConfig extends CamelConfiguration {
#Bean(name = "myFirstList")
public DataFormat getMyFirstListDataFormat() {
return new MyFirstListDataFormat();
}
#Bean(name = "mySecondList")
public DataFormat getMySecondListDataFormat() {
return new MySecondListDataFormat();
}
#Bean
public RouteBuilder route() {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:testFirstDataFormat").setHeader("dataFormatBeanName", constant("myFirstList")).to("direct:myRoute");
from("direct:testSecondDataFormat").setHeader("dataFormatBeanName", constant("mySecondList")).to("direct:myRoute");
}
};
}
}
}
Second route is supposed to retrieve the bean name from the header and use it as a custom marshaller. Something like :
custom(header("dataFormatBeanName"))
(doesn't compile)
Anyone knows how I'm supposed to get my bean name from the header to use it in the custom method ?
#Component
public class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
final RouteDefinition routedefinition = this.from("direct:myRoute");
routedefinition.marshal().custom(??????????).to("netty4:tcp://{{route.address}}:{{port}}?textline=true&sync=true");
}
After a few more hours searching, here is the solution a found :
No changes in the first class.
Second class uses an anonymous DataFormat in which I retrieve the bean name from the header and get the spring bean from camel context before calling its marshal method.
The AbstractXxxDataFormat class belongs to project2 and is inherited by the Project1 DataFormat.
#Override
public void configure() throws Exception {
final RouteDefinition routedefinition = this.from("direct:myRoute");
routedefinition.marshal(new DataFormat() {
#Override
public void marshal(final Exchange exchange, final Object graph, final OutputStream stream) throws Exception {
AbstractXxxDataFormat myDataFormat = (AbstractGoalDataFormat) getContext().getRegistry().lookupByName(exchange.getIn().getHeader("dataFormatBeanName", String.class));
myDataFormat.marshal(exchange, graph, stream);
}
#Override
public Object unmarshal(final Exchange exchange, final InputStream stream) throws Exception {
return null;
}
});
routedefinition.to("netty4:tcp://{{route.address}}:{{port}}?textline=true&sync=true");
}
If there's any better solution available, I'll be interested.
Have you tried simple("${header.dataFormatBeanName}") to access the header?
Also, rather than passing the format bean name in a header in the first place, why not factor out each .marshal() call into two subroutes (one for formatBeanA and one for formatBeanB) and then call the appropriate subroute rather than setting the header in the first place? I believe this could be a cleaner approach.
If you really need to get it in the route as a variable (as opposed to a predicate to be used in the builder api) you could use an inline processor to extract it:
public class MyRouteBuilder extends RouteBuilder {
public void configure() throws Exception {
from("someEndpoint")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
String beanName = exchange.getHeader("beanNameHeader");
}
});
}
}
Just be careful of scope and concurrency when storing the extracted beanName however.
A collegue of mine (thanks to him) found the definite solution :
set bean name in the exchange properties :
exchange.setProperty("myDataFormat", "myDataFormatAutowiredBean");
retrieve the dataFormat bean with RecipientList pattern and (un)marshal :
routedefinition.recipientList(simple("dataformat:${property.myDataFormat}:marshal"));
routedefinition.recipientList(simple("dataformat:${property.myDataFormat}:unmarshal"));
Very concise and works just fine.

Abstraction with repository pattern

I am building an application (a web api to be specific) and I want to implement the repository pattern to abstract the data access layer and prepare it for future changes.
My goal is to make the repositories interfaces abstract enough to be able to implement every technology on top of them, starting from Native SQL Client (running sql command) to orm's like EF or dapper.
I have read some articles about repositories and the interface of my generic repository looks something like that:
interface IRepository<T>
{
IEnumerable<T> FindAll();
IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate);
T FindById(int id);
void Add(T entity);
void Remove(T entity);
}
I want the method FindBy to accept a linq expression because the other option is making it accept native sql and that won't work too well with technologies like linq to entity of EF.
The problem is that i also want to be able to implement a native sql repository on top of this interface and in order to implement a native sql repository, i need to run sql command, strings.
In this interface i don't accept any sql command as string, i accept linq expressions, and the native sql client can't handle linq expressions (as far as i know).
So my question is, how can i make this interface be compatible with any technology/orm/library/client/adapter, you get the idea...
Thanks,
Arik
Adbstracting repository is a good idea. I'll try to help you.
First of all, your repository must independent of persistance, so you need to remove method FindBy(Expression<Func<T, bool>> predicate), rather replace it with some kind of specification pattern:
interface IRepository<T>
{
IEnumerable<T> FindAll();
IEnumerable<T> FindBy(ISpecification<T> specification);
T FindById(int id);
void Add(T entity);
void Remove(T entity);
}
public interface ISpecification<T>
{
IEnumerable<T> Execute(DbContext context);
}
So, now we have independent repository. And then you can create implementation of specification and repository for linq-supported persistance, it can looks like this:
public class LinqRepository<T> : IRepository<T>
{
private readonly DbContext _context;
public LinqRepository(DbContext context)
{
_context = context;
}
public IEnumerable<T> FindBy(ISpecification<T> specification)
{
return specification.Execute(_context);
}
//and others...
}
public class LinqSpecification<T> : ISpecification<T>
{
private readonly Expression<Func<T, bool>> _predicate;
public LinqSpecification(Expression<Func<T, bool>> predicate)
{
this._predicate = predicate;
}
public IEnumerable<T> Execute(DbContext context)
{
return context.Set<T>().Where(_predicate).ToList();
}
}
And call like this:
IRepository<Person> repository = new LinqRepository<Person>(dbContext);
var adults = repository.FindBy(new LinqSpecification<Person>(p => p.Age > 18));
Another side, when you need to implement non-linq repository, it can look like this:
public class SqlRepository<T> : IRepository<T>
{
private readonly DbContext _context;
public SqlRepository(DbContext context)
{
_context = context;
}
public IEnumerable<T> FindBy(ISpecification<T> specification)
{
return specification.Execute(_context);
}
}
public class SqlSpecification<T> : ISpecification<T>
{
private readonly string _query;
public SqlSpecification(string query)
{
_query = query;
}
public IEnumerable<T> Execute(DbContext context)
{
return context.ExecuteSql<T>(_query);
}
}
And call:
IRepository<Person> repository = new SqlRepository<Person>(dbContext);
var adults = repository.FindBy(new SqlSpecification<Person>("SELECT * FROM Persons WHERE Age > 18"));
Of couse, it's not ideal variant, but it depends on your system architecture and other components. But it can be used as background.

ejb in session context is allways null

i have created sigleton session bean which keeps one connection to my mongo database. It works well in jax-rs class when using #EJB annotation - after controller is contructed and bean is injected it calls init method anotated with #PostConstruct.
Then i created similar class, which is implementing SecurityContext. I used same pattern as in controller, but it is not working properly. init() method is never called and EJB instance is always null.
So is there a way to inject EJB to my SecurityContext implemetation ? it works well unless i try to inject and use MongoConnection
my singleton session bean I use to connect mongo database:
#Singleton
#Startup
public class MongoConnection {
#PostConstruct
public void init() {
// initialize properties
}
I use it in JAX-RS controller. it works here, also in classes inherited from EntityController.
Produces(MediaType.APPLICATION_JSON)
public class EntityController extends Application {
#Context
private UriInfo context;
**#EJB
protected MongoConnection connection;**
public EntityController() {
#PostConstruct
void init() {
...
connection.getMongo();
connection.getDatabaseName();
...
}
}
I implemented my own security context, which is looking for loged user roles in mongo database.
public class MongoSecurityContext implements SecurityContext {
**#EJB
private MongoConnection connection;**
public MongoSecurityContext() {
}
#PostConstruct
void init() {
...
connection.getMongo();
connection.getDatabaseName();
...
}
public MongoSecurityContext(ContainerRequestContext requestContext) {
token = requestContext.getHeaderString("token");
}
#Override
public boolean isUserInRole(String roleName) {
//**connection is allways null**, so it returns false;
if (connection == null)
return false;
}
}
EDIT:
I forget, i also have this warning in glassfish 4 console:
A provider extremeteacher.mongo.connection.MongoConnectionEjb registered in SERVER runtime does not implement any provider interfaces applicable in the SERVER runtime. Due to constraint configuration problems the provider extremeteacher.mongo.connection.MongoConnectionEjb will be ignored
EDIT2:
#Provider
#Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) {
requestContext.setSecurityContext(new MongoSecurityContext(requestContext)) ;
}
}
Injection does not work for objects created with new because the container is never given control to perform the injection. I recommend moving the #EJB to the filter and passing it to the MongoSecurityContext constructor.

NHibernate: How to inject dependency on an entity

NHibernate 3.2/Fluent NHibernate 1.3/StructureMap 2.6.3 -
Trying to follow DDD as an architectural strategy, I typically don't have dependencies on domain entities. However, I'm experimenting right now with adding more behavior to my domain entities so that they are not so anemic. Everything was going well until I hooked up NHibernate. I've got two issues:
NH requires a parameterless constructor and I'd rather not have a
ctor that shouldn't be used.
When NH tries to instantiate my entity, it needs to resolve my
dependencies but I haven't given NH anything with which it can do
that.
I've been reading on the web, but most (if not all) of the examples I have found are outdated (or just old). Even though the NH camp probably doesn't approve of what I'm doing, I'm looking for the NH way to do this.
The solution ended up an implementation of NHibernate's IInterceptor. It is actually a very simple implementation when you inherit from EmptyInterceptor and override JUST the Instantiate() and SetSession() methods. Here's my interceptor using StructureMap:
public class DependencyInjectionEntityInterceptor : EmptyInterceptor
{
IContainer _container;
ISession _session;
public DependencyInjectionEntityInterceptor(IContainer container)
{
_container = container;
}
public override void SetSession(ISession session)
{
_session = session;
}
public override object Instantiate(string clazz, EntityMode entityMode, object id)
{
if (entityMode == EntityMode.Poco)
{
var type = Assembly.GetAssembly(typeof (SomeClass)).GetTypes().FirstOrDefault(x => x.FullName == clazz);
var hasParameters = type.GetConstructors().Any(x => x.GetParameters().Any());
if (type != null && hasParameters)
{
var instance = _container.GetInstance(type);
var md = _session.SessionFactory.GetClassMetadata(clazz);
md.SetIdentifier(instance, id, entityMode);
return instance;
}
}
return base.Instantiate(clazz, entityMode, id);
}
}
Then, all you have to do is tell NHibernate to use your interceptor:
public FluentConfiguration GetFluentConfiguration(IContainer container)
{
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(c => c.FromConnectionStringWithKey("Database"))
.ShowSql())
.Mappings(m =>
m.AutoMappings.Add(AutoMap.AssemblyOf<SomeClass>()))
.ExposeConfiguration(x =>
x.SetInterceptor(new DependencyInjectionEntityInterceptor(container)));
}
When I was researching this, some suggested passing in the SessionFactory into the ctor of the interceptor class. Honestly, from a session management perspective, this approach would be better.
If you need additional dependencies in your entities don't use constructor injection. Instead create an additional parameter in the entity method.
Now you will ask yourself how do you get the dependency. For this you can use CommandHandlers and Commands. The command handler takes the dependency within its constructor and calls the method of the entity. In the UI you create a command message and send it to a command processor which is responsible for calling the correct command handler.
I hope my explanation is comprehensible to you.
Domain:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public void SendNotification(string message, INotifier notifier)
{
notifier.SendMessage(string.Format("Message for customer '{0}' ({1}): {2}", Name, Id, message));
}
}
The INotifier infrastructure component is passed through the method and not the constructor!
Infrastructure:
public interface INotifier
{
void SendMessage(string message);
}
class EmailNotifier : INotifier
{
public void SendMessage(string message)
{
// SmtpClient...
}
}
class SMSNotifier : INotifier
{
public void SendMessage(string message)
{
// SMS ...
}
}
Command and CommandHandler:
public class NotificationCommandHandler : ICommandHandler<NotificationCommand>
{
private readonly INotifier _notifier;
public NotificationCommandHandler(INotifier notifier)
{
_notifier = notifier;
}
public void Execute(NotificationCommand commandMessage)
{
commandMessage.Employee.SendNotification(commandMessage.Message, _notifier);
}
}
public class NotificationCommand
{
public string Message { get; set; }
public Employee Employee { get; set; }
}
The CommandHandler gets the INotifier through constructor injection. So you do not need to use your IoC Container like a ServiceLocator.
Usage i.e. in the UI in a controller:
public class Controller
{
private readonly IMessageProcessor _messageProcessor;
public Controller(IMessageProcessor messageProcessor)
{
_messageProcessor = messageProcessor;
}
public void SendNotification (Employee employee, string message)
{
var sendMailCommand = new NotificationCommand
{
Employee = employee,
Message = message
};
_messageProcessor.Process(sendMailCommand);
}
}
If you have questions about the command processor have a look at the mvccontrib project or ask a separate question.
Sorry my previous answer didn't address the specific question. I did some more research, and it looks like I have much more to learn about when and when not to use an anemic domain model. Regarding your question, I found this article to be very on topic. It is on java, not c#, but the principles are the same. Hope this helps.