node seneca_使用Node.js和Seneca编写国际象棋微服务,第2部分

node seneca

处理新需求而无需重构 (Handling new requirements without refactoring)

Part 1 of this series talked about defining and calling microservices using Seneca. A handful of services were created to return all legal moves of a lone chess piece on a chessboard. The series continues in Part 3.

本系列的第1部分讨论了使用Seneca定义和调用微服务。 创建了一些服务,以返回棋盘上一个单独的棋子的所有合法举动。 该系列在第3部分中继续。

快速回顾: (A quick review:)
  • Seneca services are identified by a pattern consisting of role and cmd properties. Additional properties can be added to the pattern as well.

    Seneca服务由包含rolecmd属性的模式标识。 附加属性也可以添加到模式中。

this.add({
        role: "movement",
        cmd: "legalMoves"   //, otherProp: value, ...
    }, (msg, reply) => {...}
  • Services also have an implementation that takes a msg object and a reply callback. Themsg object contains the pattern properties in addition to all other data sent to the service.

    服务还具有一个接受msg对象和回复回调的实现。 msg对象除了发送到该服务的所有其他数据之外,还包含模式属性。

  • Seneca.act()is used to indirectly invoke a service. The act method takes an object and a callback function. The object contains the role, cmd, and other properties that comprise the message to the service.

    Seneca.act()用于间接调用服务。 act方法带有一个对象和一个回调函数。 该对象包含rolecmd和其他属性,这些属性构成向服务发送的消息。

seneca.act({
            role: "movement",
            cmd: "legalMoves",
            piece: p,
            board: board
        }, (err, msg) => {

There were a handful of services defined in the first part of this series. One of threerawMoves services took a piece and its position as parameters and returned 15 x 15 movement mask. These were truncated to an 8 x 8 board using alegalSquares service. The result was that the services together can return all the legal moves of any piece on any legal square of the otherwise empty chessboard.

在本系列的第一部分中定义了一些服务。 三个rawMoves服务之一将一块及其位置作为参数,并返回15 x 15移动蒙版。 使用legalSquares服务将它们截断成8 x 8的木板。 结果是这些服务可以一起返回原本空的棋盘的任何合法广场上的任何棋子的所有合法移动。

微服务和技术债务 (Microservices and Technical Debt)

One of the motivations for microservices is to reduce technical debt. Every project has deadlines and, as they loom larger, expediency often trumps quality. FIXME and TODO comments litter the source code after a while. Those comments identify technical debt that “someday” will be taken care of.

微服务的动机之一是减少技术债务 。 每个项目都有最后期限,并且随着期限的增加,权宜之计往往比质量重要。 FIXME和TODO注释过了一会儿会乱码源代码。 这些评论指出了“某天”将得到解决的技术债务。

总有一天永远不会来 (Someday never comes)

Microservices focus on functional decomposition and loose coupling. Neither of those are new ideas, but it is a rethinking about how to implement those concepts. A microservice should be small, single-purposed, and extensible. Extending a service can happen with few or no side-effects. A new service can extend an existing service, and neither the old service nor the client that once called it will know the service implementation changed. Less refactoring of classes, methods, method signatures, process flow… all this makes it easier to deal with dreaded TD.

微服务专注于功能分解和松散耦合。 这些都不是新想法,但这是对如何实现这些概念的重新思​​考。 微服务应小型,单一用途且可扩展。 扩展服务几乎没有副作用。 新服务可以扩展现有服务,并且旧服务或曾经调用它的客户端都不会知道服务实现已更改。 更少的类,方法,方法签名,过程流的重构……所有这些使得处理可怕的TD更容易。

回到游戏进行中… (Back to the game in progress…)

Moving a single chess piece around a lonely board is not really all that entertaining. In a real chess game the chessboard is shared with friendly and hostile pieces, and these impact each other’s movement.

在一个孤独的棋盘上移动单个棋子并不是真正有趣的事情。 在真正的国际象棋游戏中,国际象棋棋盘与友善而敌对的棋子共享在一起,这些棋子相互影响着对方的动作。

Right now I have alegalSquares service which can be the basis of a more completelegalMovesservice. If you recall, the legalSquares service would invoke a rawMovesservice, then remove all the ‘bad’ squares that didn’t belong on a chessboard.

现在,我有一个legalSquares服务,可以作为更完整的legalMoves服务的基础。 如果您还记得的话, legalSquares服务将调用rawMoves服务,然后删除所有不属于棋盘的“坏”正方形。

The new legalMoves service will take into account other pieces, something that legalSquares didn’t. This requires an extra parameter, one called board. The board is just going to be an array of ChessPiece instances, and will assume that the pieces on the board have already been checked for validity. For instance, two pieces don’t occupy the same square, pawns aren’t on the first rank, kings aren’t be next to each other, and so forth.

新的legalMoves服务将考虑其他部分,而legalSquares没有。 这需要一个额外的参数,称为boardboard将只是一个ChessPiece实例的数组,并将假定棋盘上的棋子已经过有效性检查。 例如,两个棋子不在同一广场上,棋子不在第一位,国王不在彼此之间,依此类推。

The following pattern will identify the service:

以下模式将标识服务:

'role: movement;cmd: legalMoves'

This pattern is a stringified version of JSON called jsonic; you can use a regular JSON object if you prefer. The message to the service will contain the pattern. It will also contain a ChessPiece instance that has a piece type such as ‘K’ing, ‘Q’ueen, ‘R’ook and board position (see algebraic notation). Later I’ll add to this class a piece color (White or Black) so that the service can tell friend from foe. But for now the service will assume all pieces are friendly.

此模式是JSON的字符串化版本,称为jsonic ; 您可以根据需要使用常规JSON对象。 发送给服务的消息将包含该模式。 它还将包含一个ChessPiece实例,其实例类型为“ K”,“ Q”,“ R”和棋盘位置(参见代数表示法)。 稍后,我将在该类中添加一块颜色(白色或黑色),以便该服务可以将敌人告诉朋友。 但就目前而言,该服务将假定所有部件都友好。

Since a friendly piece cannot be captured, it will restrict movement of other friendly pieces. Determining those restrictions is a bit of work. I made it harder for myself in the implementation of the rawMoves service… which brings me to:

由于无法捕获一个友善的棋子,它将限制其他友善棋子的移动。 确定这些限制需要一些工作。 在执行rawMoves服务时,我自己变得更加困难……这使我从事以下工作:

微服务不是万能药 (Microservices are not a Panacea)

If you design a service that retrieves or calculates information and doesn’t pass that data on up the chain, some service upstream may have to redo that work later. In my example, rawMoves returned an array of move objects (file and rank positions on the board). Let’s take the method that generates diagonal moves for a piece using the rawMoves service:

如果您设计的服务可以检索或计算信息, 但不会在链上传递该数据,则上游的某些服务可能必须稍后重做。 在我的示例中, rawMoves返回了一组移动对象(板上的文件和排名位置)。 让我们采用使用rawMoves服务为一块生成对角线移动的方法:

module.exports = function diagonal(position, range = 7) {
    var moves = [];
    const cFile = position.file.charCodeAt()
    const cRank = position.rank.charCodeAt();
    
for (var i = 1; i < range + 1; i++) {
        moves.push({
            file: String.fromCharCode(cFile - i),
            rank: String.fromCharCode(cRank - i)
        });
        moves.push({
            file: String.fromCharCode(cFile + i),
            rank: String.fromCharCode(cRank + i)
        });
        moves.push({
            file: String.fromCharCode(cFile - i),
            rank: String.fromCharCode(cRank + i)
        });
        moves.push({
            file: String.fromCharCode(cFile + i),
            rank: String.fromCharCode(cRank - i)
        });
    }
    return moves;
}

At first glance, there’s nothing wrong with this. But, those fourmove.push operations actually operate along movement vectors. I could have constructed four movement vectors, then returned a list of moves by concatenating them, like so:

乍看之下,这没有什么错。 但是,这四个move.push操作实际上是沿着运动矢量进行操作的。 我本可以构造四个运动矢量,然后通过将它们连接起来返回一个运动列表,如下所示:

function diagonalMoves(position, range) {
    var vectors = [[], [], [], []];
    const cFile = position.file.charCodeAt()
    const cRank = position.rank.charCodeAt();

    for (var i = 1; i < range + 1; i++) {
        vectors[0].push({
            file: String.fromCharCode(cFile - i),
            rank: String.fromCharCode(cRank - i)
        });
        vectors[1].push({
            file: String.fromCharCode(cFile + i),
            rank: String.fromCharCode(cRank + i)
        });
        vectors[2].push({
            file: String.fromCharCode(cFile - i),
            rank: String.fromCharCode(cRank + i)
        });
        vectors[3].push({
            file: String.fromCharCode(cFile + i),
            rank: String.fromCharCode(cRank - i)
        });
    }

    const moves = Array.prototype.concat(...vectors)
    return moves;
}

As it stood, there was no point in doing this. But later on those vectors would have come in handy for truncating movements along diagonals (or ranks or files) when a friendly piece is in the way. Instead, I had to decompose the move list along vectors in services upstream — more work and inefficiency which you will see later.

就目前而言,这样做是没有意义的。 但是后来,当友好片段出现时,这些矢量将可以方便地沿对角线(或等级或文件)截断运动。 取而代之的是,我不得不沿着上游服务中的向量分解移动列表,这会增加工作量和效率,这将在以后看到。

The real flaw, though, was that I returned an array, rather than a data object. Data objects have properties that are extendable, not so arrays. As a consequence, all my upstream services depend on receiving a movement array, and only a movement array. No flexibility. I can’t now add a list of movement vectors in addition to a move list. But I could if I had returned an object from this method and the service that called it instead.

但是,真正的缺陷是我返回了一个数组,而不是一个数据对象。 数据对象具有可扩展的属性,而不是数组。 结果,我所有的上游服务都依赖于接收移动数组, 并且只有一个运动数组。 没有灵活性。 我现在不能添加除了运动向量的列表 到移动列表。 但是我可以从该方法和调用它的服务中返回一个对象。

Lesson learned? Consider returning data objects from your services. Have your upstream services work on parts of the data, but pass all data they receive back upstream. Exceptions to this rule will abound, of course.

学过的知识? 考虑从服务中返回数据对象。 让上游服务处理部分数据,但将它们接收的所有数据传回上游。 当然,会有很多例外。

和像这样的朋友一起... (With Friends like These…)

In Part 1, there was a service under the pattern:

在第1部分中,使用以下模式提供服务:

role:"movement",cmd:"legalSquares"

role:"movement",cmd:"legalSquares"

It returned all moves of an unimpeded piece. Since this will be the base service for determining legal moves on a populated chessboard, I’ll rename the cmdto legalMoves. Now I want to extend that to take into account friendly pieces that might be blocking a path of my chosen piece.

它返回了一块畅通无阻的动作。 由于这将是确定填充棋盘上法律动作的基本服务,因此我将cmd重命名为legalMoves 。 现在,我想扩展该范围,以考虑到可能会阻碍我选择的作品路径的友好作品。

扩展服务 (The extended service)

The service that extends role:"movement",cmd:"legalMoves" is… role:"movement",cmd:"legalMoves" !

扩展role:"movement",cmd:"legalMoves"是… role:"movement",cmd:"legalMoves"

Yep, it has the same service pattern as the service it calls. You may recall that services are identified by pattern, and so how it this going to work? When the program acts on role:"movement",cmd:"legalMoves", it will use the most recently defined service. But the new service has to call the formerlegalMoves service. That can be solved easily:

是的,它具有与其调用的服务相同的服务模式。 您可能还记得,服务是通过模式来标识的,那么它如何工作? 当程序执行role:"movement",cmd:"legalMoves" ,它将使用最新定义的服务。 但是新服务必须调用以前的legalMoves服务。 这很容易解决:

this.add({
        role: "movement",
        cmd: "legalMoves"
    }, (msg, reply) => {//returns unimpeded moves}
    
this.add('role:movement,cmd:legalMoves', function (msg, reply) {
        this.
prior(msg, function (err, moves) {
            if (msg.board) {
                const boardMoves = legalMovesWithBoard(msg, moves);
                reply(err, boardMoves);
                return;
            }
            reply(err, moves);
        });
    });

This new service is able to call the former service by using the prior() method in Seneca. If no board parameter is supplied in the incoming msg object, then this service will just act as a pass-thru to the former service. But what if there is a board?

通过使用Seneca中的Priority prior()方法,此新服务可以调用以前的服务。 如果传入的msg对象中未提供board参数,则此服务将msg当前一个服务的直通。 但是,如果有董事会呢?

I’m not going to show a complete code listing here (see link below), but the gist of it is:

我不会在此处显示完整的代码清单(请参见下面的链接),但是要点是:

module.exports = function (msg, moves) {
    if (!msg.board) return moves;
    
const blockers = moves.filter(m => {
        return (msg.board.pieceAt(m))
    })
    
var newMoves = [];
    const pp = msg.piece.position;
    
const rangeChecks = {
        B: diagonalChecks,
        R: rankAndFileChecks,
        K: panopticonChecks,
        Q: panopticonChecks,
        P: pawnChecks,
        N: knightChecks
    };
    
var rangeCheck = rangeChecks[msg.piece.piece];
    // console.error(msg.piece.piece, rangeCheck.name)
    newMoves = moves.filter(m => {
        return rangeCheck(m, blockers, pp);
    })
    return newMoves;
}

Remember our old friend diagonalMoves from the rawMoves service? In order to do a range check on diagonals without handy vectors, the new legalMoves service calls this:

还记得我们来自rawMoves服务的老朋友diagonalMoves rawMoves吗? 为了对不带方便向量的对角线进行范围检查,新的legalMoves服务将其称为:

Ugly, no? I’d be happy if some algorithmically-inclined reader reduced this to two lines in the comments section. Three, even.

丑陋,不是吗? 如果某些有算法倾向的读者在评论部分将其减少到两行,我将感到高兴。 三,甚至。

So that takes care of friendly pieces. The next installment will deal with hostile pieces, which can be captured.

这样就可以处理友好的片段。 下一部分将处理可捕获的敌对碎片。

Full source code for this article can be found at GitHub.

可以在GitHub上找到本文的完整源代码。

翻译自: https://www.freecodecamp.org/news/follow-the-rules-with-seneca-ii-c22074debac/

node seneca

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值