Sorting self-referencing aggregate roots - domain-driven-design

I'm using Domain-Driven Design and CQRS on my project and I have a self-referencing aggregate root to model a hierarchical structure:
Category
CategoryId $id
CategoryName $name
?CategoryId $parentId
CategoryPosition $position
Now I want to create a feature to sort the category tree. I receive the following structure on an API controller:
[
{
"id": "ffb03f1d-dade-4f45-80d5-6e28fea17e70",
"children": [
{
"id": "bdb82eb6-59ea-4645-ae70-d03b5e01c4b6",
"children": [
{
"id": "0ad9c916-eb64-4b0a-b2d0-6864b81b304f",
"children": []
}
]
},
{
"id": "afa58caf-a0b4-4787-b784-ed3344af2b2b",
"children": [
{
"id": "6f334ab9-47b3-4f7b-8f87-f9c36db8afe5",
"children": []
}
]
}
]
},
{
"id": "7d3b6110-c282-42b3-b649-efc9394fa5f0",
"children": []
}
]
Currently I'm recursively traversing the new tree in infrastructure handling a MoveCategory command to change parentId and position for each category in the tree.
I cannot do it with a single command, because each command should modify a single Category.
Also I'm checking that tree depth is not over 4 levels, which is a domain rule.
The question is, how can I traverse the new tree and do changes on domain layer?

Related

Structural ordering for search

I am looking for a way to implement a structural ordering for a search. I use Azure search and have indexes (simplified):
[
{
"id": Guid,
"name": string,
"folderId": Guid
}
]
name field is the field I am executing the search queries against. And the folder - obviously, the folder the object lives in.
Suppose I have a folder structure:
[
{
"id": "a595885e-520e-4fd2-9bdd-3f494f187b2e",
"name": "folder1"
"searchObjects": [],
"folders": [
{
"id": "f760f2bd-7291-49ed-9be2-9546ce57fb87",
"name": "subfolder1",
"searchObjects": [],
"folders": []
}
]
},
{
"id": "200ff3b6-310a-49d1-ad99-aed6f34a8f38",
"name": "folder2",
"searchObjects": [],
"folders": []
}
]
And each of these folders has 3000 searchable objects.
What I would like to achieve is I want to paginate the search results and retrieve these pages accordingly to the folders structure. For example, let's say I query 5000 objects with each request. In this case, I would get:
1 page - 3000 items from folder1 + 2000 items from subfolder1;
2 page - 1000 items from subfolder1 + 3000 items from folder2;
The initial thought was to calculate a certain folder index before putting the searchable objects into Azure Search. e.g. folder index:
[
{
"index": 1
"name": "folder1"
"folders": [
{
"index": 11,
"name": "subfolder1"
},
{
"index": 12,
"name": "subfolder2"
},
{
"index": 13,
"name": "subfolder3"
"folders": [
{
"index": 131,
"name": "subSubfolder1"
}
]
}
]
},
{
"index": 2
"name": "folder2"
"folders": [
{
"index": 21,
"name": "subfolder2"
}
]
}
]
Searchable objects:
[
{
"id": "3d4374ec-18a0-4e5b-bb55-e7576b475cdb",
"name": "this object is in folder1",
"folderIndex": 1
},
{
"id": "3d4374ec-18a0-4e5b-bb55-e7576b475cdb",
"name": "this object is in subSubfolder1",
"folderIndex": 131
},
{
"id": "2c2c02ec-3f57-4c85-886e-df6603718d44",
"name": "this object is in subfolder1",
"folderIndex": 11
},
...
]
This would allow me to search by the name and order by the folder structure:
search=this object&$top=5000&$searchFields=name&$orderby=folderIndex,name
When I put/change one or even a thousand of objects in a folder it works fine, I just index/reindex these objects on Azure Search side. But it doesn't work in scale. I may have hundreds of folders folded into each other and each of these folders may contain thousands of objects. So if I reorganize the folders it becomes a mess. I have to recalculate almost all of the objects starting from the top folder in the changing tree down to the bottom leaves.
This would be much easier with a relational structure where I could store folders with their indexes separately from the searchable objects, join them by folder IDs and order by the folder indexer all the same, but ...
Is there a way of doing this right?
Is the folder index being kept just for the reason of ordering the result set by folder path? If that's the case, why not keep full folder paths as a sortable field in the original index? This way you'll be able to order the result set by folder paths, assuming the folder path order you want is alphabetical.
For example:
Doc1: “field1”
Doc2: ”field1”
Doc3: “field1\subfield11\subfield111”
Doc4: ”field2”

How to create a field mapping in Azure Search with a complex targetField

I use the Azure Search indexer to index documents from a MongoDB CosmosDB which contains objects with fields named _id.
As Azure Search does not allow underscores at the beginning of a field name in the index, I want to create a field mapping.
JSON structure in Cosmos --> structure in index
{
"id": "test"
"name": "test",
"productLine": {
"_id": "123", --> "id": "123"
"name": "test"
}
}
The documentation has exactly this scenario as an example but only for a top level field.
"fieldMappings" : [ { "sourceFieldName" : "_id", "targetFieldName" : "id" } ]}
I tried the following:
"fieldMappings" : [ { "sourceFieldName" : "productLine/_id", "targetFieldName" : "productLine/id" } ] }
that results in an error stating:
Value is not accepted. Valid values: "doc_id", "name", "productName".
What is the correct way to create a mapping for a target field that is a subfield?
It's not possible to directly map subfields. You can get around this by adding a Skillset with a Shaper cognitive skill to the indexer, and an output field mapping.
You will also want to attach a Cognitive Services resource to the skillset. The shaper skill doesn't get billed, but attaching a Cognitive Services resource allows you to process more than 20 documents per day.
Shaper skill
{
"#odata.type": "#Microsoft.Skills.Util.ShaperSkill",
"context": "/document",
"inputs": [
{
"name": "id",
"source": "/document/productLine/_id"
},
{
"name": "name",
"source": "/document/productLine/name"
}
],
"outputs": [
{
"name": "output",
"targetName": "renamedProductLine"
}
]
}
Indexer skillset and output field mapping
"skillsetName": <skillsetName>,
"outputFieldMappings": [
{
"sourceFieldName": "/document/renamedProductLine",
"targetFieldName": "productLine"
}
]

How to correctly query parent & child documents in SOLR?

Index Structure:
{
"documents": [
{
"doc_type": [
"dataset"
],
"title": [
"document_title"
],
"id": [
1234
],
"_childDocuments_": [
{
"parent_id": [
1234
],
"doc_type": [
"column"
],
"id": [
789
],
"attr_nm": [
"child_field_value"
],
},
]
}
]
}
Whenever a search/query is performed I always want to return the parent document. E.g. If I search for 'child_field_value' I want to get the parent document ad the child documents are never seen by the user.
I can get this working for parents. E.g. if I search for 'document_title' I can get the structure above using the following setup:
q=document_title
fl=*,[child parentFilter=doc_type:dataset]
qf=title^200 attr_nm
If I try this using the query keyword "child_field_value" I get a strange structure like:
"response": {
"numFound":2,
"start":0,
"docs":[
{
"parent_id":"1234",
"doc_type":"column",
"id":"123",
"attr_nm":"child_field_value",
},
{
"parent_id":"1234",
"doc_type":"column",
"id":"456",
"attr_nm":"child_field_value",
"_childDocuments_":[{
"parent_id":"1234",
"doc_type":"column",
"id":"124",
"attr_nm":"child_field_value",
}]
}
]
}
How can I ensure always get the desired results even if searching a child field for any value?

how to query nested array of objects in mongodb?

i am trying to query nested array of objects in mongodb from node js, tried all the solutions but no luck. can anyone please help this on priority?
I have tried following :
{
"name": "Science",
"chapters": [
{
"name": "ScienceChap1",
"tests": [
{
"name": "ScienceChap1Test1",
"id": 1,
"marks": 10,
"duration": 30,
"questions": [
{
"question": "What is the capital city of New Mexico?",
"type": "mcq",
"choice": [
"Guadalajara",
"Albuquerque",
"Santa Fe",
"Taos"
],
"answer": [
"Santa Fe",
"Taos"
]
},
{
"question": "Who is the author of beowulf?",
"type": "notmcq",
"choice": [
"Mark Twain",
"Shakespeare",
"Abraham Lincoln",
"Newton"
],
"answer": [
"Shakespeare"
]
}
]
},
{
"name": "ScienceChap1test2",
"id": 2,
"marks": 20,
"duration": 30,
"questions": [
{
"question": "What is the capital city of New Mexico?",
"type": "mcq",
"choice": [
"Guadalajara",
"Albuquerque",
"Santa Fe",
"Taos"
],
"answer": [
"Santa Fe",
"Taos"
]
},
{
"question": "Who is the author of beowulf?",
"type": "notmcq",
"choice": [
"Mark Twain",
"Shakespeare",
"Abraham Lincoln",
"Newton"
],
"answer": [
"Shakespeare"
]
}
]
}
]
}
]
}
Here is what I've tried so far but still can't get it to work
db.quiz.find({name:"Science"},{"tests":0,chapters:{$elemMatch:{name:"ScienceCh‌​ap1"}}})
db.quiz.find({ chapters: { $elemMatch: {$elemMatch: { name:"ScienceChap1Test1" } } }})
db.quiz.find({name:"Science"},{chapters:{$elemMatch:{$elemMatch:{name:"Scienc‌​eChap1Test1"}}}}) ({ name:"Science"},{ chapters: { $elemMatch: {$elemMatch: { name:"ScienceChap1Test1" } } }})
Aggregation Framework
You can use the aggregation framework to transform and combine documents in a collection to display to the client. You build a pipeline that processes a stream of documents through several building blocks: filtering, projecting, grouping, sorting, etc.
If you want get the mcq type questions from the test named "ScienceChap1Test1", you would do the following:
db.quiz.aggregate(
//Match the documents by query. Search for science course
{"$match":{"name":"Science"}},
//De-normalize the nested array of chapters.
{"$unwind":"$chapters"},
{"$unwind":"$chapters.tests"},
//Match the document with test name Science Chapter
{"$match":{"chapters.tests.name":"ScienceChap1test2"}},
//Unwind nested questions array
{"$unwind":"$chapters.tests.questions"},
//Match questions of type mcq
{"$match":{"chapters.tests.questions.type":"mcq"}}
).pretty()
The result will be:
{
"_id" : ObjectId("5629eb252e95c020d4a0c5a5"),
"name" : "Science",
"chapters" : {
"name" : "ScienceChap1",
"tests" : {
"name" : "ScienceChap1test2",
"id" : 2,
"marks" : 20,
"duration" : 30,
"questions" : {
"question" : "What is the capital city of New Mexico?",
"type" : "mcq",
"choice" : [
"Guadalajara",
"Albuquerque",
"Santa Fe",
"Taos"
],
"answer" : [
"Santa Fe",
"Taos"
]
}
}
}
}
$elemMatch doesn't work for sub documents. You can use the aggregation framework for "array filtering" by using $unwind.
You can delete each line from the bottom of each command in the aggregation pipeline in the above code to observe the pipelines behavior.
You should try the following queries in the mongodb simple javascript shell.
There could be Two Scenarios.
Scenario One
If you simply want to return the documents that contain certain chapter names or test names for example just one argument in find will do.
For the find method the document you want to be returned is specified by the first argument. You could return documents with the name Science by doing this:
db.quiz.find({name:"Science"})
You could specify criteria to match a single embedded document in an array by using $elemMatch. To find a document that has a chapter with the name ScienceChap1. You could do this:
db.quiz.find({"chapters":{"$elemMatch":{"name":"ScienceChap1"}}})
If you wanted your criteria to be a test name then you could use the dot operator like this:
db.quiz.find({"chapters.tests":{"$elemMatch":{"name":"ScienceChap1Test1"}}})
Scenario Two - Specifying Which Keys to Return
If you want to specify which keys to Return you can pass a second argument to find (or findOne) specifying the keys you want. In your case you can search for the document name and then provide which keys to return like so.
db.quiz.find({name:"Science"},{"chapters":1})
//Would return
{
"_id": ObjectId(...),
"chapters": [
"name": "ScienceChap2",
"tests: [..all object content here..]
}
If you only want to return the marks from each test object you can use the dot operator to do so:
db.quiz.find({name:"Science"},{"chapters.tests.marks":1})
//Would return
{
"_id": ObjectId(...),
"chapters": [
"tests: [
{"marks":10},
{"marks":20}
]
}
If you only want to return the questions from each test:
db.quiz.find({name:"Science"},{"chapters.tests.questions":1})
Test these out. I hope these help.

Marklogic 8 Node.js API - How can I scope a search on a property child of root?

[updated 17:15 on 28/09]
I'm manipulating json data of type:
[
{
"id": 1,
"title": "Sun",
"seeAlso": [
{
"id": 2,
"title": "Rain"
},
{
"id": 3,
"title": "Cloud"
}
]
},
{
"id": 2,
"title": "Rain",
"seeAlso": [
{
"id": 3,
"title": "Cloud"
}
]
},
{
"id": 3,
"title": "Cloud",
"seeAlso": [
{
"id": 1,
"title": "Sun"
}
]
},
];
After inclusion in the database, a node.js search using
db.documents.query(
q.where(
q.collection('test films'),
q.value('title','Sun')
).withOptions({categories: 'none'})
)
.result( function(results) {
console.log(JSON.stringify(results, null,2));
});
will return both the film titled 'Sun' and the films which have a seeAlso/title property (forgive the xpath syntax) = 'Sun'.
I need to find 1/ films with title = 'Sun' 2/ films with seeAlso/title = 'Sun'.
I tried a container query using q.scope() with no success; I don't find how to scope the root object node (first case) and for the second case,
q.where(q.scope(q.property('seeAlso'), q.value('title','Sun')))
returns as first result an item which matches all text inside the root object node
{
"index": 1,
"uri": "/1.json",
"path": "fn:doc(\"/1.json\")",
"score": 137216,
"confidence": 0.6202662,
"fitness": 0.6701325,
"href": "/v1/documents?uri=%2F1.json&database=Documents",
"mimetype": "application/json",
"format": "json",
"matches": [
{
"path": "fn:doc(\"/1.json\")/object-node()",
"match-text": [
"Sun Rain Cloud"
]
}
]
},
which seems crazy.
Any idea about how doing such searches on denormalized json data?
Laurent:
XPaths on JSON are supported by MarkLogic.
In particular, you might consider setting up a path range index to match /title at the root:
http://docs.marklogic.com/guide/admin/range_index#id_54948
Scoped property matching required either filtering or indexed positions to be accurate. An alternative is to set up another path range index on /seeAlso/title
For the match issue it would be useful to know the MarkLogic version and to see the entire query.
Hoping that helps,

Resources