DDD - service extension

120 views
Skip to first unread message

Alexandre Parshin

unread,
Apr 19, 2018, 3:18:42 AM4/19/18
to DDD/CQRS

I am writing a multi-tenancy Platform As A Service solution, and trying to employ DDD approach. I have some basic, Core functionality which is default behaviour and has its services exosed to an API (application) layer. So far it all was straightforward, until I've started to write per-tenant, custom functionality (separated projects on top of Core). How do I extend base services to add custom behaviour?

For example, Core service:

namespace Core.Services
{
    public class UserService
    {
        private readonly IUserRepository userRepository;

        public User Get(Guid id)
        {
            userRepository.GetById(id);
        }

        public void Update(Guid id, UserStatus newUserStatus)
        {
            userRepository.UpdateStatus(Guid id, newUserStatus);
        }
    }
}

Now, I have a ClientA, who wants to, say, periodically rename users based on their activity and some other stuff:

namespace ClientA.Services
{
    public class UserService // : Core.UserService ?
    {
        public void RenameInactiveUsers()
        {
            // something like
            userRepository.UpdateAll(
                condition: (user) => { user.NotActiveRecently() },
                action: (user) => { user.Name = user.Name + " (inactive)" }
            )
        }

        public void BanSuspiciousUsers()
        {
            userRepository.UpdateAll(
                condition: (user) => { user.SomeSuspiciousFlag },
                action: (user) => { user.Status = UserStatus::Banned }
            );
        }

        // etc
    }
}

So, how do I extend base services while still expose Get, Update and other methods to reuse the code?

Should it be inheritance? That way I will have to make private repositories, data and methods accessible in ancestors. Furthermore, inheritance in discourage in DDD as far as I know.

Or should for each tenant I create new UserService, which takes Core.UserService and expose identical contract, which will lead to many code like ClientA.UserService.Get(id) { return udnerlyingUserService.Get(id); } ?

Maybe, some third option?

David Leangen

unread,
Apr 23, 2018, 5:20:36 AM4/23/18
to DDD/CQRS
(Replying again from the web interface, as somehow my email is not getting accepted... Apologies if this eventually shows up in duplicate.)

Hi Alexander,

I noticed that nobody has replied yet. I usually don’t write replies to this group because I still feel a bit like an amateur in CQRS, but I do exactly what you are describing, so I thought I’d share my solution with you.

I work in an OSGi environment, and I doubt anybody else works the way I do, but perhaps you can think of an analogous approach in your environment. I will have to use a bit of OSGi jargon when explaining what I do.


Each Service is backed by an abstraction of a Repository. The Repository is backed by a DataStore, where the actual data is persisted. Both the Repository and the DataStore are OSGi ServiceFactories. So, there is actually one instance of each Repository for each tenant. (In my system, there are only 100s of tenants, so this solutions works for me, even on a small server.) My Service gets wired to the Repository that gets instantiated for that tenant. The Service is “tenant-aware”, so it will choose the right Repository for the tenant it is serving (depending on who is authenticated), and do its job.

Although I try not to customize, occasionally the need arises. If there is no customization, then there is only a single Service and a single Repository ServiceFactory, and I use the same one as my “template” for all tenants. For that tenant, it’s DataStore gets wired to its Repository, which of course gets wired to the Service. (The Service keeps a map of all Repositories for each tenant.)

However, if I need to customize, I simply create a new Service. If I require a different data schema as well, then I also create a separate Repository ServiceFactory. In that case I can simply configure a filter to determine which factory will instantiate the service. (I just need to be careful that I don’t misconfigure the filter or else it will break the system.)

As for the Service, I will create a completely new class, even if the customization is very small. I prefer this approach because it makes it much easier, IMO, to show where I customize. Not only do I write a separate class for the Customized Service, I could even put the customized Service in a different package, or a different bundle all together. The place doesn’t matter, but the point is that it’s not hidden deep in the code.


It gives a lot of flexibility, actually. I can have several different versions running at the same time, I can migrate some companies and not others when I want to change a schema, and I can do customizations as you describe. I can even use completely different DataStores per tenant or group of tenants or whatever. I think it helps to embrace all these differences while still maximizing code and resource reuse. I’m not sure how well this would scale, though, as it does create a LOT of service instances.

This solution works for me, anyway. :-)

Let me know if you need any more information. Happy to share.


Cheers,
=David

Alexandre Parshin

unread,
Apr 24, 2018, 2:44:02 AM4/24/18
to ddd...@googlegroups.com
Thank you, David
Very appreciated. I will need some time to settle thingd down in my head regarding your view.

Yours faithfully,
Alex

--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+unsubscribe@googlegroups.com.
Visit this group at https://groups.google.com/group/dddcqrs.
For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages