DDD: Syncing bounded contexts causing different domain behavior/logic - domain-driven-design

I am currently working on a DDD system that is composed out of several bounded contexts. 2 of them are:
Context "account management": Only staff members are allowed to work here. The idea is to manage customer accounts (like address, phone numbers, contacts etc etc) and to verify the account of a customer (basically checking if the data the customer supplied is valid).
Context "website": I can login as a customer and edit my data (change my address for example)
Here is the issue:
A user logged in into the account management context is per definition an employee. So I can assume that changes made here are "trustworthy" in the sense of "the data is verified". A simplified variant of the appservice looks like this:
class AccountAppService
{
public function changeAddress(string $accountId, string $address) : void
{
$account = $this->accountRepository->ofId(new Guid($accountId));
$account->changeAddress(new Address($address));
}
{
This is the appservice I am calling when an employee is changing an address. Note that there is no IdentityService that I inject/use in order to know who the employee is as this is not interesting here. The Account entity would emit an AccountAddressChanged event after successfully calling its changeAddress() method like so
class Account implements Entity
{
public function changeAddress(Address $address) : void
{
$this->address = $address;
DomainEventSubscriber::instance()->publish(new AccountAddressChanged($this));
}
}
But I also need to reflect changes as soon as a customer edits data on the website. I plan to do this async via events a la "AccountAddressChangedViaWebsite". The account management context will subscribe and handle that event, setting the corresponding account to "unverified" again. So a simplified subscriber of the account management context could look like:
class AccountAddressChangedViaWebsiteSubscriber
{
public function handle(AccountAddressChangedViaWebsite $event) : void
{
$accountId = $event->accountId();
$address = $event->getAddress();
$this->accountService->changeAddress($accountId, $address);
}
}
Now the question: Employees call the appservice directly, customers via subscribers. If we say "we have to reverify an account after the customer updates his data" it sounds like a domain concept.
Domain concepts fit into entities or domain services, but not into application services or subscribers for what I know. It implies to me that the following should be avoided (note the last line calling unverifyAccount()):
class AccountAddressChangedViaWebsiteSubscriber
{
public function handle(AccountAddressChangedViaWebsite $event) : void
{
$accountId = $event->accountId();
$address = $event->getAddress();
$this->accountService->changeAddress($accountId, $address);
$this->accountService->unverifyAccount($accountId);
}
}
This is domain logic that is somewhat hidden in a subscriber which seems odd. I have the gut feeling that this should be the responsibility of a domain service, but how would the domain service know that it is called by an external event (via subscriber) or a command?
I could pass a sort of "Originator" ValueObject that tells me wheter the user causing this is an employee or an external system. Example:
class OriginatorService
{
public function changeAddress(Originator $originator, Account $account, Address $address) : void
{
$account->changeAddress($address);
if(($originator instanceof Employee) === false) {
$account->unverify();
}
}
}
Here I delegate the responsibility of what to do to a domain service. But might double dispatching the OriginatorService into the Account entity be a good solution? This way the entity could check who caused the change via asking the passed in originatorService and could unverify itself.
I guess I am going down the DDD rabbit hole here, but what are your experiences/best practises in such a case?

The simplest answer is probably introduce UnverifiedAddress as a concept in your model, rather than trying to treat "Address" as a universal idea with the verification bolted on as an afterthought.

Related

how to build aggregate in ddd, passing VO or raw data?

What is the correct way of designing aggregate in DDD, for exmaple you need to create some user, and to create it we need to have id, email, living address and born address.
So we can do it like this (I'll use PHP as it's my main language):
class User {
private function __construct(
private UserId $id,
private Email $email,
private Address $livingAddress,
private Address $bornAddress
) {}
public static create(
string $uuid,
string $email,
string $city,
string $country,
string $address,
string $bornCity,
string $bornCountry,
string $bornAddress,
) {
return new self (
UsesId::fromString($uuid),
new Email($email),
new Address($city, $country, $address),
new Address($bornCity, $bornCountry, $bornAddress)
);
}
}
in this case we following a rule that aggregate root responsible of checking all invariants , because he creating all VO's and they has own validation. But it's adding complexity to actual User class, we have to many parameters passed etc.
Another possible solution is to build it like this:
class User {
private function __construct(
private UserId $id,
private Email $email,
private Address $livingAddress,
private Address $bornAddress
) {}
public static create(
UserId $id,
Email $email,
Address $livingAddress,
Address $bornAddress
) {
return new self (
id,
$email,
$livingAddress,
$bornAddress
);
}
}
now User object is smaller, maybe even more "elegant", but we breaking the rule that aggregate should check all invariants.
And the third option is probably to use Factory, I'll not provide code here but explain how I see it. Basically factory takes a row data, create VO, and passing it to aggregate, so aggregate creation will look like in the second example. Again aggregate is not responsible for all invariants, but Factory is on one of allowed patterns by DDD so I think thats fine.
I know probably there is no a right way, but I want to hear some best practices and suggestions from someone who has distinct knowledge in DDD about how to do it right.
we breaking the rule that aggregate should check all invariants.
I think you are misunderstanding this rule.
The aggregate is responsible for domain dynamics: how information changes over time. In particular, it is responsible for ensuring that the information stored is internally consistent.
That's not the same problem as input validation, which as a rule we want to solve as close to the boundary as we can manage.
Imagine, if you will, a form submission that looks like
...&userId=alphabet-soup&...
Sure, "alphabet-soup" is a string, but it's not consistent with the schemas described in RFC 4122, so something has gone Very Wrong, and we should be bailing out with a client error rather than forwarding the suspect data to the domain model.
See also Parse, Don't Validate (Alexis King, 2019)

How to access Shopware\Core\System\SalesChannel\SalesChannelContext in Entity Related event subscribers in shopware 6?

I have built a custom subscriber in my plugin for Shopware 6 that subscribes to
\Shopware\Core\Content\Product\ProductEvents::PRODUCT_WRITTEN_EVENT = 'product.written';
public function onProductWrittenEntity(EntityWrittenEvent $event): void
{
//$event->getContext() is returning the Shopware\Core\Framework\Context
}
I want to get domain URL of this current salesChannel having those productIds which are currently written. how can i do that?
You can obtain the salesChannelId by running the following code:
$event->getContext()->getSource()->getSalesChannelId()
With that salesChannelId and inserting the SalesChannelRepository via the services.xml into your Subscriber, you can load the required information from that sales-channel.
When you edit the products over the API or inside the administration, you are in a "admin context", that means no sales-channel is available. This is because your changes are globally and you are not limited to a specific sales-channel.
The SalesChannelContext is only available if the action that was triggered originated in the storefront or came over the store-api.
Long story short:
You can't access the salesChannelContext from the EntityWrittenEvent, as most of the times there is no specific SalesChannel, where the event was triggered.
Maybe you can explain your use case a little bit more, so we can suggest alternatives.
in case someone run in this Problem:
You can for example Subscribe to the Event "SalesChannelContextResolvedEvent". Store all the Data in a variable as type array (Argument2 from the construct, "$this->saleschannelContext"). And call it in where ever you need it, for example an other event (you can call it so -> "$this->salechannelContext").
public function __construct(EntityRepository $discountExtensionRepository){
$this->discountExtensionRepository = $discountExtensionRepository;
$this->salechannelContext = array();
}
public static function getSubscribedEvents(): array{
return [
SalesChannelContextResolvedEvent::class => "onPageLoaded",
ProductEvents::PRODUCT_LOADED_EVENT => 'onProductsLoaded'
];
}
public function onPageLoaded(SalesChannelContextResolvedEvent $event){
$this->salechannelContext = $event->getSaleschannelContext();
}
public function onProductsLoaded(EntityLoadedEvent $event):void{
dump($this->salechannelContext);
}
Probably not the best practice way because i guess there is a way to get it directly from die Product event, but it is one way of manys.
[EDIT]: You can get all Storefront and Product informations with this event.
use Shopware\Core\System\SalesChannel\Entity\SalesChannelEntityLoadedEvent;
public static function getSubscribedEvents(): array{
return [
'sales_channel.product.loaded' => 'onSalesChannelLoaded'
];
}
public function onSalesChannelLoaded(SalesChannelEntityLoadedEvent $event):void{

Validate date from other bounded context

I have two Bounded Context (studentenrollment, courses).
Studentenrollment has all the student with his course ids and his homework.
Courses have the admin part that content all the information related with the course.
When a student want to get information of the course, it hits an endpoint( /courses/ID) sending the jwt token. In the course context I get the student ID, course ID and create query that it's dispatched in the bus. In the query handler before getting the information of the course from the course ID, I want to validate if the student ID exist and this student has this course. For that I have to call the another context bounded studentenrollment. So, I was looking for how to handle that on internet and I found this:
https://medium.com/#martinezdelariva/authentication-and-authorization-in-ddd-671f7a5596ac
class findByCourseIdAndStudentIdQueryHandler()
{
public function handle($findByCourseIdAndStudentIdQuery)
{
$courseId = $findByCourseIdAndStudentIdQuery->courseId();
$studentId = $findByCourseIdAndStudentIdQuery->studentId();
$student = $this->collaboratorService->studentFrom(
$courseId,
$studentId
);
$this->courseRepository->findByCourseId($courseId);
}
}
class collaboratorService()
{
public function studentFrom($courseId, $studentId)
{
$student = $this->studentEnrollmentClient->getStudentFrom($courseId, $studentId);
if (!$student) {
throw new InvalidStudentException();
}
return $student;
}
}
What do you think?
UPDATED
namespace App\Context\Course\Module\Course\UI\Controller;
class GetCourseController extends Controller
{
public function getAction($request) {
$this->ask(new FindByCourseIdQueryHandler($request->get('course_id'));
}
}
namespace App\Context\Course\Module\Course\Infrastracture\Query;
class AuthorizedQueryDispatcher extends QueryDispatcher
{
//In this case $query would be FindByCourseIdQueryHandler
public function handle($query)
{
$authUser = $this->oauthService->getAuthUser();
//it can be student or teacher
$role = $authUser->getRole();
$userId = $authUser->getUserId();
//it will return FindByCourseIdAndStudentIdAuthorizedQueryHandler
$authorizedQuery = $this->inflector->getAuthorizedQueryName->from($userId, $role, $query);
$this->dispatch($authorizedQuery);
$this->queryDispatch->dispatch($query);
}
}
namespace App\Context\Course\Module\Course\Application\Query;
class FindByCourseIdAndStudentIdAuthorizedQueryHandler
{
public function handle($findByCourseIdAndStudentIdQuery)
{
$student = $this->studentEnrollmentClient->getStudentFrom($findByCourseIdAndStudentIdQuery->courseId, $findByCourseIdAndStudentIdQuery->studentId);
if (!$student) {
throw new InvalidStudentException();
}
}
}
namespace App\Context\Course\Module\Course\Application\Query;
class findByCourseIdAndStudentIdQueryHandler()
{
public function handle($findByCourseIdQueryHandler)
{
$courseId = $findByCourseIdQueryHandler->courseId();
$this->courseRepository->findByCourseId($courseId);
}
}
TLDR; Authorization should be clearly separated from the Domain layer, for example in a different package/namespace/module. Also, the dependency from the Domain to the Authorization should be inverted, the Domain should not depend/know about the authorization/
One way to implement it is to create an Authorization service, for example FindByCourseIdAndStudentIdQueryAuthorizer (let's name it Authorizer). This service may cross Bounded context (BC) boundaries, i.e. it could depend on remote domain services from remote BCs. Ideally, the remote data should be already available when the Authorizer does the checking. In this way the system is more resilient in case remote Bounded context services are not available. You can do this by listening to remote events or by background tasks.
Ideally, the domain layer (from any BC) should not know about the Authorizers.
One way to do this is to decorate the QueryDispatcher (or what you have) in the Composition root of the application with an AuthorizedQueryDispatcher. This AuthorizedQueryDispatcher, when it receives a query, it first search an Authorizer and then calls it. If the authorization fails then the query is rejected. If the authorization succedds or there is not authorizer then the query is sent to the real/decorated QueryDispatcher.
If can't do this (i.e. you don't have a QueryDispatcher) then you can try to decorate every query handler (by hand?). For example, you could have a FindByCourseIdAndStudentIdAuthorizedQueryHandler that has the same interface as the FindByCourseIdAndStudentIdQueryHandler. You could replace them in the composition root of the application (DIC).

ServiceStack Json Serializer ignore properties

I have a business requirement to only send permissioned properties in our response payload. For instance, our response DTO may have several properties, and one of them is SSN. If the user doesn't have permissions to view the SSN then I would never want it to be in the Json response. The second requirement is that we send null values if the client has permissions to view or change the property. Because of the second requirement setting the properties that the user cannot view to null will not work. I have to still return null values.
I have a solution that will work. I create an expandoObject by reflecting through my DTO and add only the properties that I need. This is working in my tests.
I have looked at implementing ITextSerializer. I could use that and wrap my response DTO in another object that would have a list of properties to skip. Then I could roll my own SerializeToString() and SerializeToStream(). I don't really see any other ways at this point. I can't use the JsConfig and make a SerializeFn because the properties to skip would change with each request.
So I think that implementing ITextSerializer is a good option. Are there any good examples of this getting implemented? I would really like to use all the hard work that was already done in the serializer and take advantage of the great performance. I think that in an ideal world I would just need to add a check in the WriteType.WriteProperties() to look and the property is one to write, but that is internal and really, most of them are so I can't really take advantage of them.
If someone has some insight please let me know! Maybe I am making the implementation of ITextSerialzer a lot harder that it really is?
Thanks!
Pull request #359 added the property "ExcludePropertyReference" to the JsConfig and the JsConfigScope. You can now exclude references in scope like I needed to.
I would be hesitant to write my own Serializer. I would try to find solutions that you can plug in into the existing ServiceStack code. That way you will have to worry less about updating dlls and breaking changes.
One potential solution would be decorating your properties with a Custom Attributes that you could reflect upon and obscure the property values. This could be done in the Service before Serialization even happens. This would still include values that they user does not have permission to see but I would argue that if you null those properties out they won't even be serialized by JSON anyways. If you keep all the properties the same they you will keep the benefits of strong typed DTOs.
Here is some hacky code I quickly came up with to demonstrate this. I would move this into a plugin and make the reflection faster with some sort of property caching but I think you will get the idea.
Hit the url twice using the following routes to see it in action.
/test?role
/test?role=Admin (hack to pretend to be an authenticated request)
[System.AttributeUsage(System.AttributeTargets.Property)]
public class SecureProperty : System.Attribute
{
public string Role {get;set;}
public SecureProperty(string role)
{
Role = role;
}
}
[Route("/test")]
public class Test : IReturn
{
public string Name { get; set; }
[SecureProperty("Admin")]
public string SSN { get; set; }
public string SSN2 { get; set; }
public string Role {get;set;}
}
public class TestService : Service
{
public object Get(Test request)
{
// hack to demo roles.
var usersCurrentRole = request.Role;
var props = typeof(Test).GetProperties()
.Where(
prop => ((SecureProperty[])prop
.GetCustomAttributes(typeof(SecureProperty), false))
.Any(att => att.Role != usersCurrentRole)
);
var t = new Test() {
Name = "Joe",
SSN = "123-45-6789",
SSN2 = "123-45-6789" };
foreach(var p in props) {
p.SetValue(t, "xxx-xx-xxxx", null);
}
return t;
}
}
Require().StartHost("http://localhost:8080/",
configurationBuilder: host => { });
I create this demo in ScriptCS. Check it out.

Where to check user email does not already exist?

I have an account object that creates a user like so;
public class Account
{
public ICollection<User> Users { get; set; }
public User CreateUser(string email)
{
User user = new User(email);
user.Account = this;
Users.Add(user);
}
}
In my service layer when creating a new user I call this method. However there is a rule that the users email MUST be unique to the account, so where does this go? To me it should go in the CreateUser method with an extra line that just checks that the email is unique to the account.
However if it were to do this then ALL the users for the account would need to be loaded in and that seems like a bit of an overhead to me. It would be better to query the database for the users email - but doing that in the method would require a repository in the account object wouldn't it? Maybe the answer then is when loading the account from the repository instead of doing;
var accountRepository.Get(12);
//instead do
var accountRepository.GetWithUserLoadedOnEmail(12, "someone#example.com");
Then the account object could still check the Users collection for the email and it would have been eagerly loaded in if found.
Does this work? What would you do?
I'm using NHibernate as an ORM.
First off, I do not think you should use exceptions to handle "normal" business logic like checking for duplicate email addresses. This is a well document anti-pattern and is best avoided. Keep the constraint on the DB and handle any duplicate exceptions because they cannot be avoid, but try to keep them to a minimum by checking. I would not recommend locking the table.
Secondly, you've put the DDD tag on this questions, so I'll answer it in a DDD way. It looks to me like you need a domain service or factory. Once you have moved this code in a domain service or factory, you can then inject a UserRepository into it and make a call to it to see if a user already exists with that email address.
Something like this:
public class CreateUserService
{
private readonly IUserRepository userRepository;
public CreateUserService(IUserRepository userRepository)
{
this.userRepository = userRepository;
}
public bool CreateUser(Account account, string emailAddress)
{
// Check if there is already a user with this email address
User userWithSameEmailAddress = userRepository.GetUserByEmailAddress(emailAddress);
if (userWithSameEmailAddress != null)
{
return false;
}
// Create the new user, depending on you aggregates this could be a factory method on Account
User newUser = new User(emailAddress);
account.AddUser(newUser);
return true;
}
}
This allows you to separate the responsiblities a little and use the domain service to coordinate things. Hope that helps!
If you have properly specified the constraints on the users table, the add should throw an exception telling you that there is already a duplicate value. You can either catch that exception in the CreateUser method and return null or some duplicate user status code, or let it flow out and catch it later.
You don't want to test if it exists in your code and then add, because there is a slight possibility that between the test and the add, someone will come along and add the same email with would cause the exception to be thrown anyway...
public User CreateUser(string email)
{
try
{
User user = new User(email);
user.Account = this;
user.Insert();
catch (SqlException e)
{
// It would be best to check for the exception code from your db...
return null;
}
}
Given that "the rule that the users email MUST be unique to the account", then the most important thing is to specify in the database schema that the email is unique, so that the database INSERT will fail if the email is duplicate.
You probably can't prevent two users adding the same email nearly-simultaneously, so the next thing is that the code should handle (gracefully) an INSERT failure cause by the above.
After you've implemented the above, seeing whether the email is unique before you do the insert is just optional.

Resources