CQRS in data-centric processes - domain-driven-design

I have got a question related to CQRS in data centric processes. Let me explain it better.
Consider we have a SOAP/JSON/whatever service, which transfers some data to our system during an integration process. It is said that in CQRS every state change must be achieved by the means of commands (or events if Event Sourcing is used).
When it comes to our integrating process we have got a great deal of structured DATA instead of a set of commands/events and I am wondering how to actually process those data.
// Some Façade service
class SomeService
{
$_someService;
public function __construct(SomeService $someService)
{
$this->_someService = $someService;
}
// Magic function to make it all good and
public function process($dto)
{
// if I get it correctly here I need somehow
// convert incoming dto (xml/json/array/etc)
// to a set of commands, i. e
$this->someService->doSomeStuff($dto->someStuffData);
// SomeStuffChangedEvent raised here
$this->someService->doSomeMoreStuff($dtom->someMoreStuffData);
// SomeMoreStuffChangedEvent raised here
}
}
My question is whether my suggestion is suitable in the given case or there may be some better methods to do what I need. Thank you in advance.

Agreed, a service may have a different interface. If you create a rest-api to update employees, you may want to provide an UpdateEmployeeMessage which contains everything that can change. In a CRUD-kind of service, this message would probably mirror the database.
Inside of the service, you can split the message into commands:
public void Update(UpdateEmployeeMessage message)
{
bus.Send(new UpdateName
{
EmployeeId = message.EmployeeId,
First = message.FirstName,
Last = message.LastName,
});
bus.Send(new UpdateAddress
{
EmployeeId = message.EmployeeId,
Street = message.Street,
ZipCode = message.ZipCode,
City = message.City
});
bus.Send(new UpdateContactInfo
{
EmployeeId = message.EmployeeId,
Phone = message.Phone,
Email = message.Email
});
}
Or you could call the aggregate directly:
public void Update(UpdateEmployeeMessage message)
{
var employee = repository.Get<Employee>(message.EmployeeId);
employee.UpdateName(message.FirstName, message.LastName);
employee.UpdateAddress(message.Street, message.ZipCode, message.City);
employee.UpdatePhone(message.Phone);
employee.UpdateEmail(message.Email);
repository.Save(employee);
}

Related

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).

Spring Aggregation Group

I did create an aggregate service as below
#EnableBinding(Processor.class)
class Configuration {
#Autowired
Processor processor;
#ServiceActivator(inputChannel = Processor.INPUT)
#Bean
public MessageHandler aggregator() {
AggregatingMessageHandler aggregatingMessageHandler =
new AggregatingMessageHandler(new DefaultAggregatingMessageGroupProcessor(),
new SimpleMessageStore(10));
//AggregatorFactoryBean aggregatorFactoryBean = new AggregatorFactoryBean();
//aggregatorFactoryBean.setMessageStore();
aggregatingMessageHandler.setOutputChannel(processor.output());
//aggregatorFactoryBean.setDiscardChannel(processor.output());
aggregatingMessageHandler.setSendPartialResultOnExpiry(true);
aggregatingMessageHandler.setSendTimeout(1000L);
aggregatingMessageHandler.setCorrelationStrategy(new ExpressionEvaluatingCorrelationStrategy("requestType"));
aggregatingMessageHandler.setReleaseStrategy(new MessageCountReleaseStrategy(3)); //ExpressionEvaluatingReleaseStrategy("size() == 5")
aggregatingMessageHandler.setExpireGroupsUponCompletion(true);
aggregatingMessageHandler.setGroupTimeoutExpression(new ValueExpression<>(3000L)); //size() ge 2 ? 5000 : -1
aggregatingMessageHandler.setExpireGroupsUponTimeout(true);
return aggregatingMessageHandler;
}
}
Now i want to release the group as soon as a new group is created, so i only have one group at a time.
To be more specific i do receive two types of requests 'PUT' and 'DEL' . i want to keep aggregating per the above rules but as soon as i receive a request type other than what i am aggregating i want to release the current group and start aggregating the new Type.
The reason i want to do this is because these requests are sent to another party that don't support having PUT and DEL requests at the same time and i can't delay any DEL request as sequence between PUT and DEL is important.
I understand that i need to create a custom release Pojo but will i be able to check the current groups ?
For Example
If i receive 6 messages like below
PUT PUT PUT DEL DEL PUT
they should be aggregated as below
3PUT
2 DEL
1 PUT
OK. Thank you for sharing more info.
Yes, you custom ReleaseStrategy can check that message type and return true to lead to the group completion function.
As long as you have only static correlationKey, so only one group is there in the store. When your message is stepping to the ReleaseStrategy, there won't be much magic just to check the current group for completion signal. Since there are no any other groups in the store, there is no need any complex release logic.
You should add expireGroupsUponCompletion = true to let the group to be removed after completion and the next message will form a new group for the same correlationKey.
UPDATE
Thank you for further info!
So, yes, your original PoC is good. And even static correlationKey is fine, since you are just going to collect incoming messages to batches.
Your custom ReleaseStrategy should analyze MessageGroup for a message with different key and return true in that case.
The custom MessageGroupProcessor should filter a message with different key from the output List and send that message to the aggregator back to let to form a new group for a sequence for its key.
i ended up implementing the below ReleaseStrategy as i found it simpler than removing message and queuing it again.
class MessageCountAndOnlyOneGroupReleaseStrategy implements org.springframework.integration.aggregator.ReleaseStrategy {
private final int threshold;
private final MessageGroupProcessor messageGroupProcessor;
public MessageCountAndOnlyOneGroupReleaseStrategy(int threshold,MessageGroupProcessor messageGroupProcessor) {
super();
this.threshold = threshold;
this.messageGroupProcessor = messageGroupProcessor;
}
private MessageGroup currentGroup;
#Override
public boolean canRelease(MessageGroup group) {
if(currentGroup == null)
currentGroup = group;
if(!group.getGroupId().equals(currentGroup.getGroupId())) {
messageGroupProcessor.processMessageGroup(currentGroup);
currentGroup = group;
return false;
}
return group.size() >= this.threshold;
}
}
Note that i did used new HeaderAttributeCorrelationStrategy("request_type") instead of just FOO for CollorationStrategy

Processing an emaillist async in MVC4

I'm trying to make my MVC4-website check to see if people should be alerted with an email because they haven't done something.
I'm having a hard time figuring out how to approach this. I checked if the shared hosting platform would allow me to activate some sort of cronjob, but this is not available.
So now my idea is to perform this check on each page-request, which already seems suboptimal (because of the overhead). But I thought that with using an async it would not be in the way of people just visiting the site.
I first tried to do this in the Application_BeginRequest method in Global.asax, but then it gets called multiple times per page-request, so that didn't work.
Next I found that I can make a Global Filter which executes on OnResultExecuted, which would seemed promising, but still it's no go.
The problem I get there is that I'm using MVCMailer to send the mails, and when I execute it I get the error: {"Value cannot be null.\r\nParameter name: httpContext"}
This probably means that mailer needs the context.
The code I now have in my global filter is the following:
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
HandleEmptyProfileAlerts();
}
private void HandleEmptyProfileAlerts()
{
new Thread(() =>
{
bool active = false;
new UserMailer().AlertFirst("bla#bla.com").Send();
DB db = new DB();
DateTime CutoffDate = DateTime.Now.AddDays(-5);
var ProfilesToAlert = db.UserProfiles.Where(x => x.CreatedOn < CutoffDate && !x.ProfileActive && x.AlertsSent.Where(y => y.AlertType == "First").Count() == 0).ToList();
foreach (UserProfile up in ProfilesToAlert)
{
if (active)
{
new UserMailer().AlertFirst(up.UserName).Send();
up.AlertsSent.Add(new UserAlert { AlertType = "First", DateSent = DateTime.Now, UserProfileID = up.UserId });
}
else
System.Diagnostics.Debug.WriteLine(up.UserName);
}
db.SaveChanges();
}).Start();
}
So my question is, am I going about this the right way, and if so, how can I make sure that MVCMailer gets the right context?
The usual way to do this kind of thing is to have a single background thread that periodically does the checks you're interested in.
You would start the thread from Application_Start(). It's common to use a database to queue and store work items, although it can also be done in memory if it's better for your app.

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.

Resources