Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
themeEmacs
<PackageReference Include="Neo4jClient" Version="4.1.14" />
<PackageReference Include="AkkaDotModule.Webnori" Version="1.1.1" />
Code Block
themeEmacs
titleappsetting.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "AppSettings": {    
    "GraphConnection": "http://localhost:7474",
    "GraphConnectionUser": "neo4j",
    "GraphConnectionPw": "bitnami"
  }
}
Code Block
themeEmacs
titleGraphEngine
using Neo4jClient;
using Neo4jClient.Cypher;

    public class GraphEngine
    {
        private readonly AppSettings appSettings;

        private readonly ILogger logger;

        private readonly GraphClient neo4jClient;

        public GraphEngine(AppSettings _appSettings, ILogger<GraphEngine> _logger)
        {
            appSettings = _appSettings;            
            logger = _logger;
            neo4jClient = new GraphClient(new Uri(appSettings.GraphConnection), appSettings.GraphConnectionUser, appSettings.GraphConnectionPw);            
        }

        public async Task<ICypherFluentQuery> GetCypher()
        {
            if (!neo4jClient.IsConnected)
            {
                await neo4jClient.ConnectAsync();
            }
            return neo4jClient.Cypher;
        }

        public async Task<GraphClient> GetClient()
        {
            if (!neo4jClient.IsConnected)
            {
                await neo4jClient.ConnectAsync();
            }
            return neo4jClient;
        }

        public async Task RemoveAll()
        {
            var cyper = await GetCypher();

            await cyper
                .Match("(n)")
                .DetachDelete("n")
                .ExecuteWithoutResultsAsync();

        }

    }


DI셋팅
public void ConfigureServices(IServiceCollection services)
{
	services.AddSingleton<GraphEngine>();
}

유닛테스트기를 사용하여 Graph DB활용해보기

UseCase :

  • 홍길동 사용자생성
  • 철수 사용자생성
  • 스파이더맨 영화생성
  • 타이타닉 영화생성
  • 홍길동은 스파이더맨을 보았다
  • 철수는 타이타닉을 보았다
  • 홍길동과 철수 친구연결
Code Block
themeEmacs
namespace SearchApiTest.Adapter
{
    public class GraphEngineTest
    {
        private GraphEngine _graphEngine;

        [SetUp]
        public void SetUp()
        {
            var logger = TestLogger.Create<GraphEngine>();
            var builder = new ConfigurationBuilder()
                .AddJsonFile("appsettings.Development.json");
            var Configuration = builder.Build();

            var options = new AppSettings();
            Configuration.GetSection("AppSettings")
                .Bind(options);
            _graphEngine = new GraphEngine(options, logger);
        }

        [TestCase(TestName = "Step0 - 초기화 생성및 연결 Test")]
        public async Task CreatePersonAreOK()
        {
            _graphEngine.RemoveAll().Wait();

            var cypher = await _graphEngine.GetCypher();

            await cypher.Write
                .Create(@"(alice:Person {name:'홍길동'})")
                .ExecuteWithoutResultsAsync();

            await cypher.Write
                .Create(@"(alice:Person {name:'철수'})")
                .ExecuteWithoutResultsAsync();

            await cypher.Write
                .Create(@"(alice:Movie {name:'스파이더맨'})")
                .ExecuteWithoutResultsAsync();

            await cypher.Write
                .Create(@"(alice:Movie {name:'타이타닉'})")
                .ExecuteWithoutResultsAsync();

            await cypher.Write
                .Match(@"(a: Person),(b: Movie)")
                .Where(@"a.name = '홍길동' AND b.name = '스파이더맨'")
                .Create(@"(a)-[r:뷰]->(b)").ExecuteWithoutResultsAsync();

            await cypher.Write
                .Match(@"(a: Person),(b: Movie)")
                .Where(@"a.name = '철수' AND b.name = '타이타닉'")
                .Create(@"(a)-[r:뷰]->(b)").ExecuteWithoutResultsAsync();

            await cypher.Write
                .Match(@"(a: Person),(b: Person)")
                .Where(@"a.name = '철수' AND b.name = '홍길동'")
                .Create(@"(a)-[r:친구]->(b)").ExecuteWithoutResultsAsync();
        }
    }
}


친구가본 영화를 추천하는 간단한 그래프 모델입니다.

Image Added


이벤트 큐로 확장하기

서비스에서 이벤트가 발생할때마다 Crud를 직접하는것은 서비스의 성능을 느리게할수 있으며, 발생이벤트를 메시징큐에 적재하여

백그라운드에서 순차적으로 또는 분리된 리모트에서 해당이벤트를 처리할수 있습니다. ( AkkaRemote또는 Kafka가 활용될수 있습니다.)


그래프 이벤트를 처리하는 액터

Code Block
themeEmacs
namespace SearchApi.Actors
{
    public class GraphElementIdenty
    {
        public string Alice { get; set; }

        public string Name { get; set; }
    }

    public class GraphEvent
    {
        public string Action { get; set; } // Create , Relation, Reset

        public string Alice { get; set; } // AliceName

        public string Name { get; set; }

        public GraphElementIdenty From { get; set; }

        public GraphElementIdenty To { get; set; }
    }

    public class GraphEventActor : ReceiveActor
    {
        private readonly ILoggingAdapter logger = Context.GetLogger();        
        private readonly GraphEngine graphEngine;

        public GraphEventActor(GraphEngine _graphEngine)
        {
            logger.Info($"Create GraphEventActor:{Context.Self.Path.Name}");
            graphEngine = _graphEngine;

            ReceiveAsync<GraphEvent>(async graphEvent =>
            {
                var cypher = await _graphEngine.GetCypher();

                switch (graphEvent.Action)
                {
                    case "Reset":
                        {
                            await _graphEngine.RemoveAll();
                        }
                        break;
                    case "Create":
                        {                            
                            await cypher.Write
                                .Create($"(alice:{graphEvent.Alice} {{name:'{graphEvent.Name}'}})")
                                .ExecuteWithoutResultsAsync();
                        }
                        break;
                    case "Relation":
                        {
                            await cypher.Write
                                .Match($"(a:{graphEvent.From.Alice}),(b:{graphEvent.To.Alice})")
                                .Where($"a.name = '{graphEvent.From.Name}' AND b.name = '{graphEvent.To.Name}'")
                                .Create($"(a)-[r:{graphEvent.Name}]->(b)").ExecuteWithoutResultsAsync();
                        }
                        break;
                }
            });
        }
    }
}
Code Block
themeEmacs
titleGraph 이벤트발생
var graphEngine = app.ApplicationServices.GetService<GraphEngine>();

var graphEventActor = AkkaLoad.RegisterActor(
    "GraphEventActor",
    actorSystem.ActorOf(Props.Create<GraphEventActor>(graphEngine),
        "GraphEventActor"
));

//Test For Graph
graphEventActor.Tell(new GraphEvent()
{
    Action = "Reset"
});

graphEventActor.Tell(new GraphEvent()
{
    Action = "Create",
    Alice = "Person",
    Name = "홍길동"
});

graphEventActor.Tell(new GraphEvent()
{
    Action = "Create",
    Alice = "Movie",
    Name = "스파이더맨"
});

graphEventActor.Tell(new GraphEvent()
{
    Action = "Relation",                            
    Name = "시청",
    From = new GraphElementIdenty()
    {
        Alice = "Person",
        Name = "홍길동"
    },
    To = new GraphElementIdenty()
    {
        Alice = "Movie",
        Name = "스파이더맨"
    }
});


추가참고 링크